UNPKG

@web3auth/no-modal

Version:
471 lines (467 loc) 21.9 kB
'use strict'; var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var baseControllers = require('@toruslabs/base-controllers'); var Client = require('@walletconnect/sign-client'); var utils$2 = require('@walletconnect/utils'); var wsEmbed = require('@web3auth/ws-embed'); var deepmerge = require('deepmerge'); var analytics = require('../../base/analytics.js'); var IChainInterface = require('../../base/chain/IChainInterface.js'); var baseConnector = require('../../base/connector/baseConnector.js'); var constants = require('../../base/connector/constants.js'); require('@web3auth/auth'); var utils$1 = require('../../base/connector/utils.js'); var index$1 = require('../../base/errors/index.js'); var loglevel = require('../../base/loglevel.js'); require('../../base/plugin/errors.js'); require('../../base/plugin/IPlugin.js'); var utils = require('../../base/utils.js'); var index = require('../../base/wallet/index.js'); var config = require('./config.js'); var WalletConnectV2Provider = require('./WalletConnectV2Provider.js'); class WalletConnectV2Connector extends baseConnector.BaseConnector { constructor(options) { var _this$connectorOption, _this$connectorOption2, _this$connectorOption3, _this$connectorOption4, _this$connectorOption5; super(options); _defineProperty(this, "name", index.WALLET_CONNECTORS.WALLET_CONNECT_V2); _defineProperty(this, "connectorNamespace", IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN); _defineProperty(this, "currentChainNamespace", baseControllers.CHAIN_NAMESPACES.OTHER); _defineProperty(this, "type", constants.CONNECTOR_CATEGORY.EXTERNAL); _defineProperty(this, "connectorOptions", void 0); _defineProperty(this, "status", constants.CONNECTOR_STATUS.NOT_READY); _defineProperty(this, "connectorData", { uri: "" }); _defineProperty(this, "connector", null); _defineProperty(this, "activeSession", null); _defineProperty(this, "wcProvider", null); _defineProperty(this, "analytics", void 0); this.connectorOptions = _objectSpread({}, options); const { qrcodeModal, walletConnectInitOptions } = (options === null || options === void 0 ? void 0 : options.connectorSettings) || {}; this.connectorOptions = _objectSpread(_objectSpread({}, this.connectorOptions), {}, { connectorSettings: (_this$connectorOption = (_this$connectorOption2 = this.connectorOptions) === null || _this$connectorOption2 === void 0 ? void 0 : _this$connectorOption2.connectorSettings) !== null && _this$connectorOption !== void 0 ? _this$connectorOption : {}, loginSettings: (_this$connectorOption3 = (_this$connectorOption4 = this.connectorOptions) === null || _this$connectorOption4 === void 0 ? void 0 : _this$connectorOption4.loginSettings) !== null && _this$connectorOption3 !== void 0 ? _this$connectorOption3 : {} }); this.analytics = options.analytics; if (qrcodeModal) this.connectorOptions.connectorSettings.qrcodeModal = qrcodeModal; if (walletConnectInitOptions) this.connectorOptions.connectorSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$connectorOption5 = this.connectorOptions.connectorSettings.walletConnectInitOptions) !== null && _this$connectorOption5 !== void 0 ? _this$connectorOption5 : {}), walletConnectInitOptions); const { loginSettings } = options; if (loginSettings) this.connectorOptions.loginSettings = _objectSpread(_objectSpread({}, this.connectorOptions.loginSettings || {}), loginSettings); } get connected() { return !!this.activeSession; } get provider() { if (this.status !== constants.CONNECTOR_STATUS.NOT_READY && this.wcProvider) { return this.wcProvider; } return null; } set provider(_) { throw new Error("Not implemented"); } async init(options) { var _this$connectorOption6; const chainConfig = this.coreOptions.chains.find(x => x.chainId === options.chainId); super.checkInitializationRequirements({ chainConfig }); const projectId = (_this$connectorOption6 = this.connectorOptions.connectorSettings) === null || _this$connectorOption6 === void 0 || (_this$connectorOption6 = _this$connectorOption6.walletConnectInitOptions) === null || _this$connectorOption6 === void 0 ? void 0 : _this$connectorOption6.projectId; const filteredChains = this.coreOptions.chains.filter(x => x.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || x.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA); if (filteredChains.length === 0) { throw index$1.WalletInitializationError.invalidParams("No supported chains found"); } const wc2Settings = await config.getWalletConnectV2Settings(filteredChains, projectId); if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) { this.connectorOptions.loginSettings = wc2Settings.loginSettings; } this.connectorOptions.connectorSettings = deepmerge(wc2Settings.connectorSettings || {}, this.connectorOptions.connectorSettings || {}); const { connectorSettings } = this.connectorOptions; this.connector = await Client.init(connectorSettings === null || connectorSettings === void 0 ? void 0 : connectorSettings.walletConnectInitOptions); this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({ config: { chain: chainConfig, chains: this.coreOptions.chains }, connector: this.connector }); this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2); this.status = constants.CONNECTOR_STATUS.READY; loglevel.log.debug("initializing wallet connect v2 connector"); if (options.autoConnect) { await this.checkForPersistedSession(); if (this.connected) { this.rehydrated = true; try { await this.onConnectHandler({ chain: chainConfig }); } catch (error) { loglevel.log.error("wallet auto connect", error); this.emit(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, error); } } else { this.status = constants.CONNECTOR_STATUS.NOT_READY; this.emit(constants.CONNECTOR_EVENTS.CACHE_CLEAR); } } } async connect({ chainId }) { super.checkConnectionRequirements(); const chainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!chainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available"); if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); // Skip tracking for rehydration since only new connections are tracked // Track when connection completes since it auto-initializes to generate QR code const shouldTrack = !this.connected && !this.rehydrated; const startTime = Date.now(); let eventData = { connector: this.name, connector_type: this.type, is_injected: this.isInjected, is_wallet_connect: true, chain_id: utils.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 { const trackCompletionEvents = () => { // track connection events if (shouldTrack) { var _this$activeSession, _this$analytics, _this$analytics2; const { name, url, redirect } = ((_this$activeSession = this.activeSession) === null || _this$activeSession === void 0 || (_this$activeSession = _this$activeSession.peer) === null || _this$activeSession === void 0 ? void 0 : _this$activeSession.metadata) || {}; const { native, universal, linkMode } = redirect || {}; eventData = _objectSpread(_objectSpread({}, eventData), {}, { connector: name || this.name, wallet_url: url, redirect_native: native, redirect_universal: universal, redirect_link_mode_enabled: linkMode }); (_this$analytics = this.analytics) === null || _this$analytics === void 0 || _this$analytics.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics2 = this.analytics) === null || _this$analytics2 === void 0 || _this$analytics2.track(analytics.ANALYTICS_EVENTS.CONNECTION_COMPLETED, _objectSpread(_objectSpread({}, eventData), {}, { duration: Date.now() - startTime })); } }; // if already connected if (this.connected) { await this.onConnectHandler({ chain: chainConfig }); return this.provider; } if (this.status !== constants.CONNECTOR_STATUS.CONNECTING) { await this.createNewSession({ chainConfig, trackCompletionEvents }); } return this.provider; } catch (error) { loglevel.log.error("Wallet connect v2 connector error while connecting", error); // ready again to be connected this.status = constants.CONNECTOR_STATUS.READY; this.rehydrated = true; this.emit(constants.CONNECTOR_EVENTS.ERRORED, error); // track connection events if (shouldTrack) { var _this$analytics3, _this$analytics4; (_this$analytics3 = this.analytics) === null || _this$analytics3 === void 0 || _this$analytics3.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics4 = this.analytics) === null || _this$analytics4 === void 0 || _this$analytics4.track(analytics.ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), baseControllers.getErrorAnalyticsProperties(error)), {}, { duration: Date.now() - startTime })); } const finalError = error instanceof index$1.Web3AuthError ? error : index$1.WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`, error); throw finalError; } } async switchChain(params, init = false) { super.checkSwitchChainRequirements(params, init); if (!this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet Connect provider is not ready yet"); try { await this.wcProvider.switchChain({ chainId: params.chainId }); } catch (error) { loglevel.log.error("error while switching chain", error); throw error; } } async getUserInfo() { if (!this.connected) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); return {}; } async disconnect(options = { cleanup: false, sessionRemovedByWallet: false }) { var _this$activeSession2, _this$activeSession3; const { cleanup } = options; if (!this.connector || !this.connected || !((_this$activeSession2 = this.activeSession) !== null && _this$activeSession2 !== void 0 && _this$activeSession2.topic)) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet"); if (!options.sessionRemovedByWallet) await this.connector.disconnect({ topic: (_this$activeSession3 = this.activeSession) === null || _this$activeSession3 === void 0 ? void 0 : _this$activeSession3.topic, reason: utils$2.getSdkError("USER_DISCONNECTED") }); this.rehydrated = false; if (cleanup) { this.connector = null; this.status = constants.CONNECTOR_STATUS.NOT_READY; this.wcProvider = null; } else { // ready to connect again this.status = constants.CONNECTOR_STATUS.READY; } this.activeSession = null; this.emit(constants.CONNECTOR_EVENTS.DISCONNECTED); } async getIdentityToken() { if (!this.provider || this.status !== constants.CONNECTOR_STATUS.CONNECTED) throw index$1.WalletLoginError.notConnectedError(); const { chainId } = this.provider; const currentChainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!currentChainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available"); const { chainNamespace } = currentChainConfig; const accounts = await this.provider.request({ method: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? wsEmbed.EVM_METHOD_TYPES.GET_ACCOUNTS : wsEmbed.SOLANA_METHOD_TYPES.GET_ACCOUNTS }); if (accounts && accounts.length > 0) { const existingToken = utils$1.getSavedToken(accounts[0], this.name); if (existingToken) { const isExpired = utils$1.checkIfTokenIsExpired(existingToken); if (!isExpired) { return { idToken: existingToken }; } } const payload = { domain: window.location.origin, uri: window.location.href, address: accounts[0], chainId: parseInt(chainId, 16), version: "1", nonce: Math.random().toString(36).slice(2), issuedAt: new Date().toISOString() }; const challenge = await baseControllers.signChallenge(payload, chainNamespace); const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace); const idToken = await baseControllers.verifySignedChallenge(chainNamespace, signedMessage, challenge, this.name, this.coreOptions.sessionTime, this.coreOptions.clientId, this.coreOptions.web3AuthNetwork); utils$1.saveToken(accounts[0], this.name, idToken); return { idToken }; } throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); } async enableMFA() { throw new Error("Method Not implemented"); } async manageMFA() { throw new Error("Method Not implemented"); } cleanupPendingPairings() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); const inactivePairings = this.connector.pairing.getAll({ active: false }); if (!utils$2.isValidArray(inactivePairings)) return; inactivePairings.forEach(pairing => { if (this.connector) { this.connector.pairing.delete(pairing.topic, utils$2.getSdkError("USER_DISCONNECTED")); } }); } async checkForPersistedSession() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); if (this.connector.session.length) { const lastKeyIndex = this.connector.session.keys.length - 1; this.activeSession = this.connector.session.get(this.connector.session.keys[lastKeyIndex]); } return this.activeSession; } async createNewSession({ forceNewSession = false, chainConfig, trackCompletionEvents }) { try { var _this$activeSession4, _this$connectorOption7; if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) throw index$1.WalletInitializationError.notReady("login settings are not set yet"); this.status = constants.CONNECTOR_STATUS.CONNECTING; this.emit(constants.CONNECTOR_EVENTS.CONNECTING, { connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2 }); if (forceNewSession && (_this$activeSession4 = this.activeSession) !== null && _this$activeSession4 !== void 0 && _this$activeSession4.topic) { var _this$activeSession5; await this.connector.disconnect({ topic: (_this$activeSession5 = this.activeSession) === null || _this$activeSession5 === void 0 ? void 0 : _this$activeSession5.topic, reason: utils$2.getSdkError("USER_DISCONNECTED") }); } const { uri, approval } = await this.connector.connect(this.connectorOptions.loginSettings); const qrcodeModal = (_this$connectorOption7 = this.connectorOptions) === null || _this$connectorOption7 === void 0 || (_this$connectorOption7 = _this$connectorOption7.connectorSettings) === null || _this$connectorOption7 === void 0 ? void 0 : _this$connectorOption7.qrcodeModal; // Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing). if (uri) { if (qrcodeModal) { try { await qrcodeModal.openModal({ uri }); loglevel.log.debug("EVENT", "QR Code Modal closed"); this.status = constants.CONNECTOR_STATUS.READY; this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2); } catch (error) { loglevel.log.error("unable to open qr code modal", error); } } else { this.updateConnectorData({ uri }); } } loglevel.log.info("awaiting session approval from wallet"); // Await session approval from the wallet. const session = await approval(); this.activeSession = session; // Handle the returned session (e.g. update UI to "connected" state). await this.onConnectHandler({ chain: chainConfig, trackCompletionEvents }); if (qrcodeModal) { qrcodeModal.closeModal(); } } catch (error) { var _error$message; if ((_error$message = error.message) !== null && _error$message !== void 0 && _error$message.toLowerCase().includes("proposal expired")) { // Retry if connector status is still connecting loglevel.log.info("current connector status: ", this.status); if (this.status === constants.CONNECTOR_STATUS.CONNECTING) { loglevel.log.info("retrying to create new wallet connect session since proposal expired"); return this.createNewSession({ forceNewSession: true, chainConfig }); } if (this.status === constants.CONNECTOR_STATUS.READY) { loglevel.log.info("ignoring proposal expired error since some other connector is connected"); return; } } loglevel.log.error("error while creating new wallet connect session", error); this.emit(constants.CONNECTOR_EVENTS.ERRORED, error); throw error; } } async onConnectHandler({ chain, trackCompletionEvents }) { var _this$connectorOption8; if (!this.connector || !this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet connect connector is not ready yet"); this.subscribeEvents(); if ((_this$connectorOption8 = this.connectorOptions.connectorSettings) !== null && _this$connectorOption8 !== void 0 && _this$connectorOption8.qrcodeModal) { this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({ config: { chain, chains: this.coreOptions.chains, skipLookupNetwork: true }, connector: this.connector }); } await this.wcProvider.setupProvider(this.connector); this.cleanupPendingPairings(); this.status = constants.CONNECTOR_STATUS.CONNECTED; // track connection events if (trackCompletionEvents) trackCompletionEvents(); this.emit(constants.CONNECTOR_EVENTS.CONNECTED, { connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2, reconnected: this.rehydrated, provider: this.provider }); } subscribeEvents() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); this.connector.events.on("session_update", ({ topic, params }) => { if (!this.connector) return; const { namespaces } = params; const _session = this.connector.session.get(topic); // Overwrite the `namespaces` of the existing session with the incoming one. const updatedSession = _objectSpread(_objectSpread({}, _session), {}, { namespaces }); // Integrate the updated session state into your dapp state. this.activeSession = updatedSession; }); this.connector.events.on("session_delete", () => { // Session was deleted -> reset the dapp state, clean up from user session, etc. this.disconnect({ sessionRemovedByWallet: true }); }); } async _getSignedMessage(challenge, accounts, chainNamespace) { const signedMessage = await this.provider.request({ method: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? wsEmbed.EVM_METHOD_TYPES.PERSONAL_SIGN : wsEmbed.SOLANA_METHOD_TYPES.SIGN_MESSAGE, params: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : { data: challenge } }); return signedMessage; } } const walletConnectV2Connector = params => { return ({ coreOptions, projectConfig, analytics }) => { var _params$walletConnect; const projectId = (params === null || params === void 0 || (_params$walletConnect = params.walletConnectInitOptions) === null || _params$walletConnect === void 0 ? void 0 : _params$walletConnect.projectId) || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.walletConnectProjectId); const connectorSettings = _objectSpread(_objectSpread({}, params), {}, { walletConnectInitOptions: _objectSpread(_objectSpread({}, params === null || params === void 0 ? void 0 : params.walletConnectInitOptions), {}, { projectId: projectId }) }); return new WalletConnectV2Connector({ connectorSettings, coreOptions, analytics }); }; }; exports.walletConnectV2Connector = walletConnectV2Connector;