@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
1,027 lines (1,025 loc) • 55.7 kB
JavaScript
'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