UNPKG

@web3auth/no-modal

Version:
1,027 lines (1,025 loc) 55.7 kB
'use strict'; var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var baseControllers = require('@toruslabs/base-controllers'); var constants$1 = require('@toruslabs/constants'); var httpHelpers = require('@toruslabs/http-helpers'); var securePubSub = require('@toruslabs/secure-pub-sub'); var auth = require('@web3auth/auth'); 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 connectorStatus = require('../../base/connector/connectorStatus.js'); var constants = require('../../base/connector/constants.js'); require('jwt-decode'); require('../../base/constants.js'); var index = 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$1 = require('../../base/wallet/index.js'); var utils$1 = require('../utils.js'); var authSolanaWallet = require('./authSolanaWallet.js'); var errors = require('../../account-linking/errors.js'); var rest = require('../../account-linking/rest.js'); // Auth connections that have been deprecated and are no longer supported by the SDK. // Passing any of these as `authConnection` results in a hard error so consumers // migrate off the removed providers instead of silently continuing. const DEPRECATED_AUTH_CONNECTIONS = new Set(["farcaster"]); function assertAuthConnectionSupported(authConnection) { if (DEPRECATED_AUTH_CONNECTIONS.has(authConnection)) { throw index.WalletInitializationError.invalidParams(`Auth connection "${authConnection}" has been deprecated and is no longer supported by the Web3Auth SDKs. ` + `Please use a different authConnection value.`); } } class AuthConnector extends baseConnector.BaseConnector { constructor(params) { super(params); _defineProperty(this, "name", index$1.WALLET_CONNECTORS.AUTH); _defineProperty(this, "connectorNamespace", IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN); _defineProperty(this, "type", constants.CONNECTOR_CATEGORY.IN_APP); _defineProperty(this, "authInstance", null); _defineProperty(this, "status", constants.CONNECTOR_STATUS.NOT_READY); _defineProperty(this, "privateKeyProvider", null); _defineProperty(this, "authOptions", void 0); _defineProperty(this, "loginSettings", { authConnection: "" }); _defineProperty(this, "wsSettings", void 0); _defineProperty(this, "wsEmbedInstance", null); _defineProperty(this, "authConnectionConfig", []); _defineProperty(this, "wsEmbedInstancePromise", null); _defineProperty(this, "wsEmbedProviderListenerTarget", null); _defineProperty(this, "_solanaWallet", null); _defineProperty(this, "analytics", void 0); _defineProperty(this, "handleWsEmbedAccountsChanged", accounts => { if (accounts.length === 0) { if (!connectorStatus.CONNECTED_STATUSES.includes(this.status)) { return; } loglevel.log.info("No accounts found in the wallet, disconnecting"); void this.disconnect({ cleanup: true }).catch(error => { loglevel.log.error("Failed to disconnect auth connector after wallet accounts changed", error); }); } }); this.authOptions = params.connectorSettings; this.loginSettings = params.loginSettings || { authConnection: "" }; this.wsSettings = params.walletServicesSettings || { loginMode: wsEmbed.WS_EMBED_LOGIN_MODE.PLUGIN }; this.authConnectionConfig = params.authConnectionConfig || []; this.analytics = params.analytics || new analytics.Analytics(); } get provider() { if (this.status !== constants.CONNECTOR_STATUS.NOT_READY) { const wsEmbedProvider = this.getWsEmbedProvider(); return wsEmbedProvider || this.privateKeyProvider; } return null; } get wsEmbed() { return this.wsEmbedInstance; } get solanaWallet() { return this._solanaWallet; } set provider(_) { throw new Error("Not implemented"); } async init(options) { const { chains } = this.coreOptions; const { chainId } = options; const chainConfig = chains.find(x => x.chainId === chainId); super.checkInitializationRequirements({ chainConfig }); if (!this.coreOptions.clientId) throw index.WalletInitializationError.invalidParams("clientId is required before auth's initialization"); if (!this.authOptions) throw index.WalletInitializationError.invalidParams("authOptions is required before auth's initialization"); if (this.authConnectionConfig.length === 0) throw index.WalletInitializationError.invalidParams("authConnectionConfig is required before auth's initialization"); const isRedirectResult = this.authOptions.uxMode === auth.UX_MODE.REDIRECT; this.authOptions = _objectSpread(_objectSpread({}, this.authOptions), {}, { replaceUrlOnRedirect: isRedirectResult, useCoreKitKey: this.coreOptions.useSFAKey }); this.authInstance = new auth.Auth(_objectSpread(_objectSpread({}, this.authOptions), {}, { clientId: this.coreOptions.clientId, network: this.coreOptions.web3AuthNetwork, sdkMode: auth.SDK_MODE.IFRAME, authConnectionConfig: this.authConnectionConfig.filter(x => !x.isDefault), mfaSettings: this.coreOptions.mfaSettings })); loglevel.log.debug("initializing auth connector init", this.authOptions); // making it async here to initialize provider. const authInstancePromise = this.authInstance.init(); // Use this for xrpl cases if (this.coreOptions.privateKeyProvider) { this.privateKeyProvider = this.coreOptions.privateKeyProvider; } else { // initialize ws embed or private key provider based on chain namespace switch (chainConfig.chainNamespace) { case baseControllers.CHAIN_NAMESPACES.EIP155: case baseControllers.CHAIN_NAMESPACES.SOLANA: { const { default: WsEmbed } = await import('@web3auth/ws-embed'); this.wsEmbedInstance = new WsEmbed({ web3AuthClientId: this.coreOptions.clientId, web3AuthNetwork: this.coreOptions.web3AuthNetwork, modalZIndex: this.wsSettings.modalZIndex }); const wsSupportedChains = chains.filter(x => x.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || x.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA); this.wsEmbedInstancePromise = this.wsEmbedInstance.init(_objectSpread(_objectSpread({}, this.wsSettings), {}, { loginMode: wsEmbed.WS_EMBED_LOGIN_MODE.PLUGIN, chains: wsSupportedChains, chainId, buildEnv: this.authOptions.buildEnv, whiteLabel: _objectSpread(_objectSpread({}, this.authOptions.whiteLabel), this.wsSettings.whiteLabel) })).then(() => { this.bindWsEmbedProviderEvents(); this.wsEmbedInstancePromise = null; return; }); break; } case baseControllers.CHAIN_NAMESPACES.XRPL: throw index.WalletLoginError.connectionError("Private key provider is required for XRPL"); default: { const { CommonPrivateKeyProvider } = await Promise.resolve().then(function () { return require('../../providers/base-provider/index.js'); }); this.privateKeyProvider = new CommonPrivateKeyProvider({ config: { chain: chainConfig, chains: this.coreOptions.chains } }); } } } // wait for auth instance to be ready. loglevel.log.debug("initializing auth connector"); await authInstancePromise; this.status = constants.CONNECTOR_STATUS.READY; this.emit(constants.CONNECTOR_EVENTS.READY, index$1.WALLET_CONNECTORS.AUTH); try { const { sessionId } = this.authInstance || {}; // connect only if it is redirect result or if connect (connector is cached/already connected in same session) is true if (sessionId && (options.autoConnect || isRedirectResult)) { this.rehydrated = true; await this.connect({ chainId: options.chainId, getAuthTokenInfo: options.getAuthTokenInfo }); } else if (!sessionId && options.autoConnect) { // if here, this means that the connector is cached but the sessionId is not available. // this can happen if the sessionId has expired. // we are throwing an error to reset the cached state. throw index.WalletLoginError.connectionError("Failed to rehydrate"); } } catch (error) { this.emit(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, error); } } async connect(params) { assertAuthConnectionSupported(params === null || params === void 0 ? void 0 : params.authConnection); super.checkConnectionRequirements(); this.status = constants.CONNECTOR_STATUS.CONNECTING; this.emit(constants.CONNECTOR_EVENTS.CONNECTING, _objectSpread(_objectSpread({}, params), {}, { connector: index$1.WALLET_CONNECTORS.AUTH })); try { await this.connectWithProvider(params); return { ethereumProvider: this.provider, solanaWallet: this._solanaWallet, connectorName: this.name, connectorNamespace: this.connectorNamespace }; } catch (error) { var _error$message; loglevel.log.error("Failed to connect with auth provider", error); // ready again to be connected this.status = constants.CONNECTOR_STATUS.READY; this.emit(constants.CONNECTOR_EVENTS.ERRORED, error); if (error !== null && error !== void 0 && (_error$message = error.message) !== null && _error$message !== void 0 && _error$message.includes("user closed popup")) { throw index.WalletLoginError.popupClosed(); } else if (error instanceof index.Web3AuthError) { throw error; } throw index.WalletLoginError.connectionError("Failed to login with auth", error); } } async enableMFA(params = { authConnection: "" }) { assertAuthConnectionSupported(params === null || params === void 0 ? void 0 : params.authConnection); if (!this.connected) throw index.WalletLoginError.notConnectedError("Not connected with wallet"); if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); try { const result = await this.authInstance.enableMFA(params); // In redirect mode, the result is not available immediately, so we emit the event when the result is available. if (result) this.emit(constants.CONNECTOR_EVENTS.MFA_ENABLED, result); } catch (error) { loglevel.log.error("Failed to enable MFA with auth provider", error); if (error instanceof index.Web3AuthError) { throw error; } throw index.WalletLoginError.connectionError("Failed to enable MFA with auth", error); } } async manageMFA(params = { authConnection: "" }) { assertAuthConnectionSupported(params === null || params === void 0 ? void 0 : params.authConnection); if (!this.connected) throw index.WalletLoginError.notConnectedError("Not connected with wallet"); if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); try { await this.authInstance.manageMFA(params); } catch (error) { loglevel.log.error("Failed to manage MFA with auth provider", error); if (error instanceof index.Web3AuthError) { throw error; } throw index.WalletLoginError.connectionError("Failed to manage MFA with auth", error); } } async disconnect(options = { cleanup: false }) { if (!this.connected) throw index.WalletLoginError.notConnectedError("Not connected with wallet"); if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); this.status = constants.CONNECTOR_STATUS.DISCONNECTING; await this.authInstance.logout(); if (this.wsEmbedInstance) await this.wsEmbedInstance.logout(); if (options.cleanup) { this.status = constants.CONNECTOR_STATUS.NOT_READY; this.authInstance = null; if (this.wsEmbedInstance) this.wsEmbedInstance = null; if (this.privateKeyProvider) this.privateKeyProvider = null; } else { // ready to be connected again this.status = constants.CONNECTOR_STATUS.READY; } this.rehydrated = false; this._solanaWallet = null; this.unbindWsEmbedProviderEvents(); this.emit(constants.CONNECTOR_EVENTS.DISCONNECTED, { connector: index$1.WALLET_CONNECTORS.AUTH }); } async getAuthTokenInfo() { if (!this.canAuthorize) throw index.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); this.status = constants.CONNECTOR_STATUS.AUTHORIZING; this.emit(constants.CONNECTOR_EVENTS.AUTHORIZING, { connector: index$1.WALLET_CONNECTORS.AUTH }); const userInfo = await this.getUserInfo(); this.status = constants.CONNECTOR_STATUS.AUTHORIZED; const [accessToken, refreshToken] = await Promise.all([this.authInstance.authSessionManager.getAccessToken(), this.authInstance.authSessionManager.getRefreshToken()]); this.emit(constants.CONNECTOR_EVENTS.AUTHORIZED, { connector: index$1.WALLET_CONNECTORS.AUTH, authTokenInfo: { idToken: userInfo.idToken, accessToken, refreshToken } }); return { idToken: userInfo.idToken, accessToken, refreshToken }; } async getUserInfo() { if (!this.canAuthorize) throw index.WalletLoginError.notConnectedError("Not connected with wallet"); if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); const [userInfo, linkedAccounts] = await Promise.all([this.authInstance.getUserInfo(), this.getLinkedAccounts()]); return _objectSpread(_objectSpread({}, userInfo), {}, { linkedAccounts }); } async getLinkedAccounts() { var _citadelUserInfo$acco, _this$solanaWallet; const accessToken = await this.authInstance.authSessionManager.getAccessToken(); if (!accessToken) throw index.WalletLoginError.connectionError("Could not obtain an access token from the current AUTH session."); const citadelUserInfo = await httpHelpers.get(`${utils.citadelServerUrl(this.coreOptions.authBuildEnv)}/v1/user`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!(citadelUserInfo !== null && citadelUserInfo !== void 0 && (_citadelUserInfo$acco = citadelUserInfo.accounts) !== null && _citadelUserInfo$acco !== void 0 && _citadelUserInfo$acco.length)) return []; const currentChainNamespace = ((_this$solanaWallet = this.solanaWallet) === null || _this$solanaWallet === void 0 ? void 0 : _this$solanaWallet.accounts.length) > 0 ? baseControllers.CHAIN_NAMESPACES.SOLANA : "evm"; // Note: citadel chain namespace is "evm" for EVM chains const filteredLinkedAccounts = []; for (const account of citadelUserInfo.accounts) { const { chainNamespace, isPrimary, accountType } = account; // for now, we will take all primary accounts as a **SINGLE** linked account // we don't wanna populate the multiple primary accounts as different linked accounts // so, we hide the primary accounts for other chain namespaces // also, linked `account_abstraction` accounts are derived from the primary account, so we don't need to show them separately // TODO: revisit this logic once we have a concrete plan for handling multiple primary accounts if (isPrimary && chainNamespace && chainNamespace !== currentChainNamespace || accountType === "account_abstraction") continue; filteredLinkedAccounts.push(_objectSpread(_objectSpread({}, account), {}, { // by default, the primary account is the active account active: isPrimary })); } return filteredLinkedAccounts; } async switchChain(params, init = false) { super.checkSwitchChainRequirements(params, init); const { chainId: newChainId } = params; const { chainId: currentChainId } = this.provider; if (currentChainId === newChainId) return; const newChainConfig = this.coreOptions.chains.find(c => c.chainId === newChainId); if (!newChainConfig) throw index.WalletInitializationError.invalidParams("Chain config is not available"); if (newChainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA || newChainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) { var _this$wsEmbedInstance; if (!((_this$wsEmbedInstance = this.wsEmbedInstance) !== null && _this$wsEmbedInstance !== void 0 && _this$wsEmbedInstance.provider)) throw index.WalletInitializationError.notReady("Wallet embed is not ready"); const caipChainId = baseControllers.getCaipChainId(newChainConfig); await this.wsEmbedInstance.provider.request({ method: "wallet_switchChain", params: { chainId: caipChainId } }); } else { var _this$privateKeyProvi; await ((_this$privateKeyProvi = this.privateKeyProvider) === null || _this$privateKeyProvi === void 0 ? void 0 : _this$privateKeyProvi.switchChain(params)); } } async cleanup() { var _this$authInstance, _this$wsEmbedInstance2; await ((_this$authInstance = this.authInstance) === null || _this$authInstance === void 0 ? void 0 : _this$authInstance.cleanup()); (_this$wsEmbedInstance2 = this.wsEmbedInstance) === null || _this$wsEmbedInstance2 === void 0 || _this$wsEmbedInstance2.clearInit(); this._solanaWallet = null; this.unbindWsEmbedProviderEvents(); } getOAuthProviderConfig(params) { const { authConnection, authConnectionId, groupedAuthConnectionId } = params; const providerConfig = this.authConnectionConfig.find(x => { if (groupedAuthConnectionId && authConnectionId) { return x.authConnection === authConnection && x.groupedAuthConnectionId === groupedAuthConnectionId && x.authConnectionId === authConnectionId; } if (authConnectionId) { return x.authConnection === authConnection && x.authConnectionId === authConnectionId; } // return the default auth connection, if not found, return undefined return x.authConnection === authConnection && x.isDefault; }); return providerConfig; } async generateChallengeAndSign() { // we do not support this for auth connector, as of now. since auth login returns a valid idToken throw new Error("Not implemented"); } async switchAccount(account, context) { if (!connectorStatus.CONNECTED_STATUSES.includes(this.status)) { throw index.WalletLoginError.notConnectedError("No wallet is connected. Connect with AUTH before switching accounts."); } try { var _userInfo$linkedAccou; const userInfo = await this.getUserInfo(); const linkedAccounts = (_userInfo$linkedAccou = userInfo.linkedAccounts) !== null && _userInfo$linkedAccou !== void 0 ? _userInfo$linkedAccou : []; const targetAccount = linkedAccounts.find(candidate => candidate.id === account.id); if (!targetAccount) { throw errors.AccountLinkingError.requestFailed(`No connected wallet matches account id "${account.id}". Refresh user info and try again.`); } const currentActiveAccount = context.activeAccount; const isTargetAlreadyActive = currentActiveAccount ? currentActiveAccount.id === targetAccount.id : targetAccount.isPrimary; if (isTargetAlreadyActive) { return; } this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_SWITCH_STARTED, this.getSwitchAccountTrackData(targetAccount)); if (targetAccount.connector === index$1.WALLET_CONNECTORS.AUTH && targetAccount.isPrimary) { var _this$provider$chainI, _this$provider; const activeChainId = this.getChainIdForLinkedAccount(targetAccount, (_this$provider$chainI = (_this$provider = this.provider) === null || _this$provider === void 0 ? void 0 : _this$provider.chainId) !== null && _this$provider$chainI !== void 0 ? _this$provider$chainI : context.currentChainId); const ethereumProvider = this.provider; const solanaWallet = this.solanaWallet; if (!ethereumProvider && !solanaWallet) { throw errors.AccountLinkingError.requestFailed("Failed to restore the primary AUTH session for account switch."); } return { kind: "primary", targetAccount, activeAccount: null, activeChainId, connectorName: this.name, connectorNamespace: this.connectorNamespace, ethereumProvider, solanaWallet }; } return { kind: "external", targetAccount, activeAccount: targetAccount, activeChainId: this.getChainIdForLinkedAccount(targetAccount, context.currentChainId) }; } catch (error) { await this.trackSwitchAccountFailed(account, error); throw error; } } async trackSwitchAccountCompleted(account) { await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_SWITCH_COMPLETED, _objectSpread(_objectSpread({}, this.getSwitchAccountTrackData(account)), {}, { connector: account.connector })); } async trackSwitchAccountFailed(account, error) { await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_SWITCH_FAILED, _objectSpread(_objectSpread({}, this.getSwitchAccountTrackData(account)), utils.getErrorAnalyticsProperties(error))); } async linkAccount(params) { if (!connectorStatus.CONNECTED_STATUSES.includes(this.status)) { throw index.WalletLoginError.notConnectedError("No wallet is connected. Connect with AUTH before linking an account."); } const { connectorName, chainId, connectorToLink } = params; try { if (!connectorToLink.connected) { const connection = await connectorToLink.connect({ chainId, isAccountLinking: true }); if (!connection) { throw errors.AccountLinkingError.walletProofFailed(`Failed to connect to "${params.connectorName}" for account linking.`); } } } catch (error) { if (error instanceof index.Web3AuthError) throw error; throw errors.AccountLinkingError.walletProofFailed(error instanceof Error ? error.message : String(error), error); } const trackData = { connector: this.name, linking_connector: connectorName, chain_id: params.chainId }; try { await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_LINKING_STARTED, trackData); const { accessToken, idToken } = await this.getPrimaryAuthSession(params.authSessionTokens); const walletProof = await this.createWalletLinkingProof(params.connectorToLink, chainId); const authServerUrl = utils.citadelServerUrl(this.coreOptions.authBuildEnv); const result = await rest.makeAccountLinkingRequest(authServerUrl, accessToken, { idToken, network: walletProof.network, connector: params.connectorName, message: walletProof.challenge, signature: { s: walletProof.signature, t: walletProof.signatureType } }); this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_LINKING_COMPLETED, _objectSpread(_objectSpread({}, trackData), {}, { linked_address: walletProof.address })); return result; } catch (error) { this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_LINKING_FAILED, _objectSpread(_objectSpread({}, trackData), utils.getErrorAnalyticsProperties(error))); // disconnect the wallet connector to avoid any leftover state try { if (connectorToLink.connected) { await connectorToLink.disconnect({ cleanup: true }); } } catch (disconnectError) { loglevel.log.debug("Failed to disconnect wallet connector after linking failure", disconnectError); } throw error; } } async unlinkAccount(params) { if (!connectorStatus.CONNECTED_STATUSES.includes(this.status)) { throw index.WalletLoginError.notConnectedError("No wallet is connected. Connect with AUTH before unlinking an account."); } const { address, authSessionTokens } = params; const trackData = { connector: this.name, address }; await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_UNLINKING_STARTED, trackData); try { const { accessToken, idToken, linkedAccounts } = await this.getPrimaryAuthSession(authSessionTokens, { includeLinkedAccounts: true }); const network = this.getNetworkForUnlinkAddress(linkedAccounts, address); const authServerUrl = utils.citadelServerUrl(this.coreOptions.authBuildEnv); const result = await rest.makeAccountUnlinkingRequest(authServerUrl, accessToken, { idToken, address, network }); await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_UNLINKING_COMPLETED, _objectSpread(_objectSpread({}, trackData), {}, { linked_address: address })); return result; } catch (error) { await this.analytics.track(analytics.ANALYTICS_EVENTS.ACCOUNT_UNLINKING_FAILED, _objectSpread(_objectSpread({}, trackData), utils.getErrorAnalyticsProperties(error))); throw error; } } getChainIdForLinkedAccount(account, preferredChainId) { const accountChainNamespace = account.chainNamespace ? utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace) : null; if (preferredChainId) { const preferredChain = this.coreOptions.chains.find(chain => chain.chainId === preferredChainId); if (preferredChain && (!accountChainNamespace || preferredChain.chainNamespace === accountChainNamespace)) { return preferredChainId; } } if (accountChainNamespace) { const namespaceChain = this.coreOptions.chains.find(chain => chain.chainNamespace === accountChainNamespace); if (namespaceChain) { return namespaceChain.chainId; } } throw index.WalletInitializationError.invalidParams(`No compatible chainId found for connector "${account.connector}".`); } async assertSwitchAccountConnectorMatchesTarget(connector, account) { if (!account.chainNamespace) { throw errors.AccountLinkingError.requestFailed(`Could not determine the chain namespace for linked account "${account.eoaAddress}".`); } const chainNamespace = utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace); let connectedAddress = null; if (chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) { var _accounts$; const accounts = connector.provider ? await connector.provider.request({ method: "eth_accounts" }) : []; connectedAddress = (_accounts$ = accounts === null || accounts === void 0 ? void 0 : accounts[0]) !== null && _accounts$ !== void 0 ? _accounts$ : null; } else if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) { var _connector$solanaWall, _connector$solanaWall2; connectedAddress = (_connector$solanaWall = (_connector$solanaWall2 = connector.solanaWallet) === null || _connector$solanaWall2 === void 0 || (_connector$solanaWall2 = _connector$solanaWall2.accounts) === null || _connector$solanaWall2 === void 0 || (_connector$solanaWall2 = _connector$solanaWall2[0]) === null || _connector$solanaWall2 === void 0 ? void 0 : _connector$solanaWall2.address) !== null && _connector$solanaWall !== void 0 ? _connector$solanaWall : null; } else { throw errors.AccountLinkingError.requestFailed(`Unsupported chain namespace "${account.chainNamespace}" for linked account "${account.eoaAddress}".`); } if (!connectedAddress) { throw errors.AccountLinkingError.requestFailed(`Connector "${account.connector}" is not connected to linked account "${account.eoaAddress}". Connect the intended wallet account and try again.`); } const isExpectedAddress = chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? connectedAddress.toLowerCase() === account.eoaAddress.toLowerCase() : connectedAddress === account.eoaAddress; if (!isExpectedAddress) { throw errors.AccountLinkingError.requestFailed(`Connector "${account.connector}" is connected to "${connectedAddress}" instead of linked account "${account.eoaAddress}". Connect the intended wallet account and try again.`); } } toSwitchAccountConnectorError(account, error) { if (error instanceof errors.AccountLinkingError && error.code === 5401) { return error; } const message = error instanceof Error ? error.message : String(error); const isUnavailableConnectorError = error instanceof errors.AccountLinkingError && error.code === 5405 || /not available|not initialized|not ready/i.test(message); if (isUnavailableConnectorError) { return errors.AccountLinkingError.requestFailed(`Connector "${account.connector}" is not available for linked account "${account.eoaAddress}". Make sure the wallet is installed, unlocked, and accessible, then try again.`, error); } return errors.AccountLinkingError.requestFailed(`Failed to connect connector "${account.connector}" for linked account "${account.eoaAddress}". ${message}`, error); } getSwitchAccountTrackData(account) { var _account$eoaAddress; return { connector: this.name, account_id: account.id, account_type: account.accountType, switched_to_address: (_account$eoaAddress = account.eoaAddress) !== null && _account$eoaAddress !== void 0 ? _account$eoaAddress : null }; } async getPrimaryAuthSession(authSessionTokens, options = {}) { const { accessToken: cachedAccessToken, idToken: cachedIdToken } = authSessionTokens; const { includeLinkedAccounts = false } = options; let accessToken = cachedAccessToken; let idToken = cachedIdToken; let linkedAccounts = []; if (includeLinkedAccounts) { const userInfoPromise = this.getUserInfo(); if (!accessToken || !idToken) { var _userInfo$linkedAccou2; const [tokenInfo, userInfo] = await Promise.all([this.getAuthTokenInfo(), userInfoPromise]); accessToken = tokenInfo.accessToken; idToken = tokenInfo.idToken; linkedAccounts = (_userInfo$linkedAccou2 = userInfo.linkedAccounts) !== null && _userInfo$linkedAccou2 !== void 0 ? _userInfo$linkedAccou2 : []; } else { var _userInfo$linkedAccou3; const userInfo = await userInfoPromise; linkedAccounts = (_userInfo$linkedAccou3 = userInfo.linkedAccounts) !== null && _userInfo$linkedAccou3 !== void 0 ? _userInfo$linkedAccou3 : []; } } else if (!accessToken || !idToken) { const tokenInfo = await this.getAuthTokenInfo(); accessToken = tokenInfo.accessToken; idToken = tokenInfo.idToken; } if (!accessToken || !idToken) { throw errors.AccountLinkingError.primaryTokenNotAvailable("Could not obtain an identity token from the current AUTH session."); } return { accessToken, idToken, linkedAccounts }; } getNetworkForUnlinkAddress(accounts, address) { const matchedAccount = accounts.find(account => { var _account$address, _account$eoaAddress2; if (!account.chainNamespace || utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace) !== baseControllers.CHAIN_NAMESPACES.EIP155) { return false; } const normalizedAddress = address.toLowerCase(); return ((_account$address = account.address) === null || _account$address === void 0 ? void 0 : _account$address.toLowerCase()) === normalizedAddress || ((_account$eoaAddress2 = account.eoaAddress) === null || _account$eoaAddress2 === void 0 ? void 0 : _account$eoaAddress2.toLowerCase()) === normalizedAddress; }); if (!matchedAccount) { throw errors.AccountLinkingError.requestFailed(`No connected wallet matches address "${address}".`); } if (!matchedAccount.chainNamespace) { throw errors.AccountLinkingError.requestFailed(`Could not determine the chain namespace for address "${address}".`); } const chainNamespace = utils.parseChainNamespaceFromCitadelResponse(matchedAccount.chainNamespace); if (chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) { return "ethereum"; } if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) { return "solana"; } throw errors.AccountLinkingError.requestFailed(`Unsupported chain namespace "${matchedAccount.chainNamespace}" for address "${address}".`); } async createWalletLinkingProof(connector, chainId) { // Notify listeners that the linking wallet is about to be asked for a signature so the UI // (e.g. modal) can switch from a "connecting" loader to an "authorizing" prompt while the // user reviews the signature request inside their wallet. Emitted on the isolated wallet // connector (not the auth connector) so it doesn't mutate the global SDK status. connector.emit(constants.CONNECTOR_EVENTS.AUTHORIZING, { connector: connector.name }); // Reuse the caller's target chain so multichain wallets generate the linking // proof for the same namespace they were connected for. const { challenge, signature, chainNamespace } = await connector.generateChallengeAndSign(undefined, undefined, chainId); const address = await this.getLinkingWalletAddress(connector, chainNamespace); if (chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) { return { address, challenge, signature, signatureType: "eip191", network: "ethereum" }; } if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) { return { address, challenge, signature, signatureType: "sip99", network: "solana" }; } throw errors.AccountLinkingError.unsupportedConnector(`Connector "${connector.name}" returned unsupported chain namespace "${chainNamespace}".`); } async getLinkingWalletAddress(connector, chainNamespace) { if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) { var _connector$solanaWall3; const address = (_connector$solanaWall3 = connector.solanaWallet) === null || _connector$solanaWall3 === void 0 || (_connector$solanaWall3 = _connector$solanaWall3.accounts) === null || _connector$solanaWall3 === void 0 || (_connector$solanaWall3 = _connector$solanaWall3[0]) === null || _connector$solanaWall3 === void 0 ? void 0 : _connector$solanaWall3.address; if (!address) { throw errors.AccountLinkingError.walletProofFailed("No connected Solana account found for account linking."); } return address; } if (!connector.provider) { throw errors.AccountLinkingError.walletProofFailed("No connected EVM account found for account linking."); } const accounts = await connector.provider.request({ method: "eth_accounts" }); if (!(accounts !== null && accounts !== void 0 && accounts.length)) { throw errors.AccountLinkingError.walletProofFailed("No connected EVM account found for account linking."); } return accounts[0]; } getWsEmbedProvider() { var _this$wsEmbedInstance3, _this$wsEmbedInstance4; return (_this$wsEmbedInstance3 = (_this$wsEmbedInstance4 = this.wsEmbedInstance) === null || _this$wsEmbedInstance4 === void 0 ? void 0 : _this$wsEmbedInstance4.provider) !== null && _this$wsEmbedInstance3 !== void 0 ? _this$wsEmbedInstance3 : null; } bindWsEmbedProviderEvents() { const rawProvider = this.getWsEmbedProvider(); if (this.wsEmbedProviderListenerTarget === rawProvider) { return; } this.unbindWsEmbedProviderEvents(); if (!rawProvider) { return; } rawProvider.on("accountsChanged", this.handleWsEmbedAccountsChanged); this.wsEmbedProviderListenerTarget = rawProvider; } unbindWsEmbedProviderEvents() { if (!this.wsEmbedProviderListenerTarget) { return; } this.wsEmbedProviderListenerTarget.removeListener("accountsChanged", this.handleWsEmbedAccountsChanged); this.wsEmbedProviderListenerTarget = null; } setupSolanaWallet() { const solanaChains = this.coreOptions.chains.filter(c => c.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA); if (solanaChains.length === 0 || !this.provider) return; if (this._solanaWallet instanceof authSolanaWallet.AuthSolanaWallet) { return; } this._solanaWallet = new authSolanaWallet.AuthSolanaWallet(this.provider, solanaChains); } _getFinalPrivKey() { if (!this.authInstance) return ""; let finalPrivKey = this.authInstance.privKey; // coreKitKey is available only for custom connections by default if (this.coreOptions.useSFAKey) { // this is to check if the user has already logged in but coreKitKey is not available. // when useSFAKey is set to true. // This is to ensure that when there is no user session active, we don't throw an exception. if (this.authInstance.privKey && !this.authInstance.coreKitKey) { throw index.WalletLoginError.sfaKeyNotFound(); } finalPrivKey = this.authInstance.coreKitKey; } return finalPrivKey; } async connectWithProvider(params) { var _this$authInstance2, _params$extraLoginOpt, _this$authInstance3, _this$authInstance4; if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); const chainConfig = this.coreOptions.chains.find(x => x.chainId === params.chainId); if (!chainConfig) throw index.WalletLoginError.connectionError("Chain config is not available"); const { chainNamespace } = chainConfig; // if not logged in then login const keyAvailable = chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA ? (_this$authInstance2 = this.authInstance) === null || _this$authInstance2 === void 0 ? void 0 : _this$authInstance2.sessionId : this._getFinalPrivKey(); if (params.idToken) params.extraLoginOptions = _objectSpread(_objectSpread({}, params.extraLoginOptions), {}, { id_token: params.idToken }); if (!keyAvailable || (_params$extraLoginOpt = params.extraLoginOptions) !== null && _params$extraLoginOpt !== void 0 && _params$extraLoginOpt.id_token) { var _params$extraLoginOpt2; // always use "other" curve to return token with all keys encoded so wallet service can switch between evm and solana namespace this.loginSettings.curve = auth.SUPPORTED_KEY_CURVES.OTHER; const loginParams = deepmerge(this.loginSettings, params); if ((_params$extraLoginOpt2 = params.extraLoginOptions) !== null && _params$extraLoginOpt2 !== void 0 && _params$extraLoginOpt2.id_token) { await this.connectWithJwtLogin(loginParams); } else { await this.connectWithSocialLogin(loginParams); } } // if useSFAKey is true and privKey is available but coreKitKey is not available, throw an error if (this.coreOptions.useSFAKey && (_this$authInstance3 = this.authInstance) !== null && _this$authInstance3 !== void 0 && _this$authInstance3.privKey && !((_this$authInstance4 = this.authInstance) !== null && _this$authInstance4 !== void 0 && _this$authInstance4.coreKitKey)) { // If the user is already logged in, logout and throw an error if (this.authInstance.sessionId) { await this.authInstance.logout(); } throw index.WalletLoginError.sfaKeyNotFound("This typically occurs when the authentication method used does not provide SFA keys (e.g., default auth connection)."); } const connectorNamespace = this.connectorNamespace; // setup WS embed if chainNamespace is EIP155 or SOLANA if (chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) { // wait for ws embed instance to be ready. if (this.wsEmbedInstancePromise) await this.wsEmbedInstancePromise; const { sessionId, sessionNamespace } = this.authInstance || {}; if (sessionId) { this.wsEmbedInstance.setAccessTokenProvider(this.accessTokenProvider.bind(this)); this.bindWsEmbedProviderEvents(); const isLoggedIn = await this.wsEmbedInstance.connectWithSession({ sessionId, sessionNamespace, idToken: await this.getIdToken() }); if (isLoggedIn) { this.setupSolanaWallet(); // if getAuthTokenInfo is true, then get auth token info // No need to get auth token info for auth connector as it is already handled this.status = constants.CONNECTOR_STATUS.CONNECTED; this.emit(constants.CONNECTOR_EVENTS.CONNECTED, { connectorName: index$1.WALLET_CONNECTORS.AUTH, reconnected: this.rehydrated, ethereumProvider: this.provider, solanaWallet: this._solanaWallet, connectorNamespace }); if (params.getAuthTokenInfo) { await this.getAuthTokenInfo(); } } } } else { // setup private key provider if chainNamespace is other const finalPrivKey = this._getFinalPrivKey(); if (finalPrivKey) { await this.privateKeyProvider.setupProvider(finalPrivKey, params.chainId); this.status = constants.CONNECTOR_STATUS.CONNECTED; this.emit(constants.CONNECTOR_EVENTS.CONNECTED, { connectorName: index$1.WALLET_CONNECTORS.AUTH, ethereumProvider: this.provider, solanaWallet: this._solanaWallet, reconnected: this.rehydrated, connectorNamespace }); } } } async connectWithSocialLogin(params) { var _params$extraLoginOpt3, _params$extraLoginOpt4; const providerConfig = this.getOAuthProviderConfig({ authConnection: params.authConnection, authConnectionId: params.authConnectionId, groupedAuthConnectionId: params.groupedAuthConnectionId }); if (!(providerConfig !== null && providerConfig !== void 0 && providerConfig.authConnection)) throw index.WalletLoginError.connectionError("Invalid auth connection."); const jwtParams = _objectSpread(_objectSpread(_objectSpread({}, providerConfig.jwtParameters || {}), params.extraLoginOptions || {}), {}, { login_hint: params.loginHint || ((_params$extraLoginOpt3 = params.extraLoginOptions) === null || _params$extraLoginOpt3 === void 0 ? void 0 : _params$extraLoginOpt3.login_hint) }); const nonce = utils$1.generateNonce(); // post a message to the auth provider to indicate that login has been initiated. const loginParams = _objectSpread(_objectSpread({}, baseControllers.cloneDeep(params)), {}, { recordId: auth.generateRecordId(), loginSource: "web3auth-web" }); loginParams.extraLoginOptions = _objectSpread(_objectSpread({}, loginParams.extraLoginOptions || {}), {}, { login_hint: params.loginHint || ((_params$extraLoginOpt4 = params.extraLoginOptions) === null || _params$extraLoginOpt4 === void 0 ? void 0 : _params$extraLoginOpt4.login_hint) }); delete loginParams.chainId; const popupParams = { authConnection: params.authConnection, authConnectionId: providerConfig.authConnectionId, clientId: providerConfig.clientId || jwtParams.client_id, groupedAuthConnectionId: providerConfig.groupedAuthConnectionId, redirect_uri: `${this.authInstance.options.sdkUrl}/auth`, jwtParams, customState: { nonce, appState: params.appState, // use the default settings from the auth instance. dapp_redirect_url: this.authInstance.options.redirectUrl, uxMode: this.authInstance.options.uxMode, whiteLabel: JSON.stringify(this.authInstance.options.whiteLabel), loginParams: JSON.stringify(loginParams), version: auth.version.split(".")[0], web3AuthNetwork: this.coreOptions.web3AuthNetwork, web3AuthClientId: this.coreOptions.clientId, originData: this.getOriginData() }, web3AuthClientId: this.coreOptions.clientId, web3AuthNetwork: this.coreOptions.web3AuthNetwork, storageServerUrl: this.authInstance.options.storageServerUrl }; const loginHandler = auth.createHandler(popupParams); const verifierWindow = new auth.PopupHandler({ url: loginHandler.finalURL, timeout: 0 }); if (this.authOptions.uxMode === auth.UX_MODE.REDIRECT) return verifierWindow.redirect(this.authOptions.replaceUrlOnRedirect); let isClosedWindow = false; this.auditOAuditProgress(loginParams).catch(error => { loglevel.log.error("Error reporting `oauthInitiated` audit progress", error); }); return new Promise((resolve, reject) => { verifierWindow.open().catch(error => { loglevel.log.error("Error during login with social", error); this.authInstance.postLoginCancelledMessage(nonce); reject(error); }); // this is to close the popup when the login is finished. const securePubSub$1 = new securePubSub.SecurePubSub({ sameIpCheck: true, serverUrl: this.authInstance.options.storageServerUrl, socketUrl: this.authInstance.options.sessionSocketUrl }); securePubSub$1.subscribe(`web3auth-login-${nonce}`).then(data => { const parsedData = JSON.parse(data || "{}"); if ((parsedData === null || parsedData === void 0 ? void 0 : parsedData.message) === "login_finished") { if (parsedData !== null && parsedData !== void 0 && parsedData.error) { this.authInstance.postLoginCancelledMessage(nonce); reject(parsedData.error); } isClosedWindow = true; securePubSub$1.cleanup(); verifierWindow.close(); } return true; }).catch(error => { // swallow the error, dont need to throw. loglevel.log.error("Error during login with social", error); this.reportFailedOauthAudit(loginParams); }); verifierWindow.once("close", () => { if (!isClosedWindow) { securePubSub$1.cleanup(); this.authInstance.postLoginCancelledMessage(nonce); reject(index.WalletLoginError.popupClosed()); } }); this.authInstance.postLoginInitiatedMessage(loginParams, nonce).then(resolve).catch(error => { this.reportFailedOauthAudit(loginParams); if (error instanceof index.Web3AuthError) { throw error; } const errorMessage = error instanceof Error ? error.message : error; if (errorMessage === "Access control denied") { reject(index.WalletLoginError.userBlocked(errorMessage, error)); return; } reject(index.WalletLoginError.connectionError(errorMessage || "Failed to login with social")); }); }); } async accessTokenProvider({ forceRefresh }) { if (forceRefresh) { await this.authInstance.refreshSession(); } return this.authInstance.getAccessToken(); } async getIdToken() { if (!this.authInstance) throw index.WalletInitializationError.notReady("authInstance is not ready"); return this.authInstance.authSessionManager.getIdToken(); } getOriginData() { try { const { originData, redirectUrl } = this.authInstance.options; const origin = new URL(redirectUrl).origin; if (originData) { const dappOriginData = originData[origin]; if (dappOriginData) { return JSON.stringify({ [origin]: dappOriginData }); } } return undefined; } catch (error) { loglevel.log.error("Error getting origin data", error); return undefined; } } connectWithJwtLogin(params) { var _params$extraLoginOpt5, _params$extraLoginOpt7; const loginConfig = this.getOAuthProviderConfig({ authConnection: params.authConnection, authConnectionId: params.authConnectionId, groupedAuthConnectionId: params.groupedAuthConnectionId }); // throw error only when we cannot find the login config and authConnectionId is not provided in the params. // otherwise, we will use the params to create a new auth connection config in the auth instance. if (!(loginConfig !== null && loginConfig !== void 0 && loginConfig.authConnection) && !params.authConnectionId) throw index.WalletLoginError.connectionError("Invalid auth connection."); if (!(loginConfig !== null && loginConfig !== void 0 && loginConfig.authConnection)) { this.authInstance.options.authConnectionConfig.push({ authConnection: params.authConnection, authConnectionId: params.authConnectionId, groupedAuthConnectionId: params.groupedAuthConnectionId }); } const loginParams = baseControllers.cloneDeep(params); const finalExtraLoginOptions = _objectSpread(_objectSpread({}, (loginConfig === null || loginConfig === void 0 ? void 0 : loginConfig.jwtParameters) || {}), params.extraLoginOptions || {}); le