UNPKG

@web3auth/ws-embed

Version:

Embed script

479 lines (474 loc) 19.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var baseControllers = require('@toruslabs/base-controllers'); var auth = require('@web3auth/auth'); var communicationProvider = require('./communicationProvider.js'); var constants = require('./constants.js'); var embedUtils = require('./embedUtils.js'); var inPageProvider = require('./inPageProvider.js'); var interfaces = require('./interfaces.js'); var loglevel = require('./loglevel.js'); var siteMetadata = require('./siteMetadata.js'); // don't use destructuring for process.env cause it messes up webpack env plugin const version = "5.1.0"; const addedVersion = `v${version.split(".")[0]}` ; const WALLET_URLS_DEFAULT = { [interfaces.WS_EMBED_BUILD_ENV.TESTING]: { url: "https://develop-wallet.web3auth.io", logLevel: "debug" }, [interfaces.WS_EMBED_BUILD_ENV.DEVELOPMENT]: { url: "http://localhost:4050", logLevel: "debug" }, [interfaces.WS_EMBED_BUILD_ENV.STAGING]: { url: `https://staging-wallet.web3auth.io/${addedVersion}`, logLevel: "debug" }, [interfaces.WS_EMBED_BUILD_ENV.PRODUCTION]: { url: `https://wallet.web3auth.io/${addedVersion}`, logLevel: "error" } }; // preload for iframe doesn't work https://bugs.chromium.org/p/chromium/issues/detail?id=593267 (async function preLoadIframe() { try { if (typeof document === "undefined") return; const torusIframeHtml = document.createElement("link"); const { url: torusUrl } = WALLET_URLS_DEFAULT.production; torusIframeHtml.href = `${torusUrl}/frame`; torusIframeHtml.crossOrigin = "anonymous"; torusIframeHtml.type = "text/html"; torusIframeHtml.rel = "prefetch"; if (torusIframeHtml.relList && torusIframeHtml.relList.supports) { if (torusIframeHtml.relList.supports("prefetch")) { document.head.appendChild(torusIframeHtml); } } } catch (error) { loglevel.warn(error); } })(); new Set([baseControllers.COMMUNICATION_JRPC_METHODS.SET_PROVIDER]); const PROVIDER_UNSAFE_METHODS = new Set([constants.SOLANA_METHOD_TYPES.SEND_TRANSACTION, constants.SOLANA_METHOD_TYPES.SIGN_MESSAGE, constants.SOLANA_METHOD_TYPES.SIGN_TRANSACTION, constants.SOLANA_METHOD_TYPES.SIGN_ALL_TRANSACTIONS, constants.EVM_METHOD_TYPES.ETH_SIGN, constants.EVM_METHOD_TYPES.ETH_SIGN_TYPED_DATA_V4, constants.EVM_METHOD_TYPES.PERSONAL_SIGN, constants.EVM_METHOD_TYPES.ETH_TRANSACTION, constants.EVM_METHOD_TYPES.ADD_CHAIN, constants.EVM_METHOD_TYPES.SWITCH_CHAIN, baseControllers.PROVIDER_JRPC_METHODS.WALLET_SWITCH_CHAIN]); class WsEmbed { constructor({ modalZIndex = 99999, web3AuthClientId, web3AuthNetwork }) { _defineProperty(this, "isInitialized", void 0); _defineProperty(this, "modalZIndex", void 0); _defineProperty(this, "provider", void 0); _defineProperty(this, "communicationProvider", void 0); _defineProperty(this, "buttonPosition", void 0); _defineProperty(this, "web3AuthClientId", void 0); _defineProperty(this, "web3AuthNetwork", void 0); _defineProperty(this, "isPluginMode", void 0); _defineProperty(this, "confirmationStrategy", void 0); _defineProperty(this, "buildEnv", void 0); _defineProperty(this, "embedNonce", baseControllers.randomId()); this.isInitialized = false; // init done this.modalZIndex = modalZIndex; this.web3AuthClientId = web3AuthClientId; this.web3AuthNetwork = web3AuthNetwork; } get isLoggedIn() { var _this$communicationPr; return ((_this$communicationPr = this.communicationProvider) === null || _this$communicationPr === void 0 ? void 0 : _this$communicationPr.isLoggedIn) || false; } get getBuildEnv() { return this.buildEnv; } async init({ buildEnv = interfaces.WS_EMBED_BUILD_ENV.PRODUCTION, enableLogging = false, chains, chainId, walletUrls, whiteLabel, confirmationStrategy = baseControllers.CONFIRMATION_STRATEGY.DEFAULT, accountAbstractionConfig, enableKeyExport = false }) { if (this.isInitialized) throw new Error("Already initialized"); this.buildEnv = buildEnv; this.confirmationStrategy = confirmationStrategy; const { url: torusUrl, logLevel } = walletUrls && walletUrls[buildEnv] ? _objectSpread(_objectSpread({}, WALLET_URLS_DEFAULT[buildEnv]), walletUrls[buildEnv]) : WALLET_URLS_DEFAULT[buildEnv]; loglevel.info(torusUrl, "url loaded"); loglevel.setDefaultLevel(logLevel); if (enableLogging) loglevel.enableAll();else loglevel.disableAll(); const torusIframeUrl = new URL(torusUrl); if (torusIframeUrl.pathname.endsWith("/")) torusIframeUrl.pathname += "frame";else torusIframeUrl.pathname += "/frame"; const hashParams = new URLSearchParams(); hashParams.append("origin", window.location.origin); hashParams.append("nonce", this.embedNonce); torusIframeUrl.hash = hashParams.toString(); const colorScheme = this.getTheme((whiteLabel === null || whiteLabel === void 0 ? void 0 : whiteLabel.mode) || auth.THEME_MODES.light); // sandbox="allow-popups allow-scripts allow-same-origin" // Iframe code const walletIframe = embedUtils.htmlToElement(`<iframe id="walletIframe-${this.embedNonce}" class="walletIframe-${this.embedNonce}" sandbox="allow-popups allow-scripts allow-same-origin allow-forms allow-modals allow-downloads" src="${torusIframeUrl.href}" style="display: none; position: fixed; top: 0; right: 0; width: 100%; height: 100%; border: none; border-radius: 0; z-index: ${this.modalZIndex.toString()}; color-scheme: ${colorScheme}" allow="clipboard-write" ></iframe>`); this.buttonPosition = (whiteLabel === null || whiteLabel === void 0 ? void 0 : whiteLabel.buttonPosition) || interfaces.BUTTON_POSITION.BOTTOM_LEFT; const dappMetadata = await siteMetadata(); return new Promise((resolve, reject) => { try { window.document.body.appendChild(walletIframe); // Wait for iframe to send ready const handleMessage = async ev => { if (ev.origin !== torusIframeUrl.origin) return; const { message, nonce } = ev.data || {}; if (message === baseControllers.SETUP_COMPLETE && nonce === this.embedNonce) { window.removeEventListener("message", handleMessage); // send init params here walletIframe.contentWindow.postMessage({ chains, chainId, dappMetadata, enableLogging, web3AuthClientId: this.web3AuthClientId, web3AuthNetwork: this.web3AuthNetwork, whiteLabel, confirmationStrategy, accountAbstractionConfig, enableKeyExport }, torusIframeUrl.origin); await this.setupWeb3({ torusUrl, walletIframe }); this.isInitialized = true; resolve(); } }; window.addEventListener("message", handleMessage); } catch (error) { reject(error); } }); } async login(params = {}) { if (!this.isInitialized) throw new Error("Call init() first"); const isAlreadyFullScreen = this.communicationProvider.isIFrameFullScreen; try { if (!params.authConnection && !isAlreadyFullScreen) { this.communicationProvider.displayIframe({ isFull: true }); } // If user is already logged in, we assume they have given access to the website const res = await new Promise((resolve, reject) => { // We use this method because we want to update inPage provider state with account info const isSolana = Object.values(constants.SOLANA_CHAIN_IDS).includes(this.provider.chainId); const method = isSolana ? constants.SOLANA_METHOD_TYPES.SOLANA_REQUEST_ACCOUNTS : constants.EVM_METHOD_TYPES.ETH_REQUEST_ACCOUNTS; this.provider.rpcRequest({ method, params: [params.authConnection, params.login_hint] }, auth.getRpcPromiseCallback(resolve, reject)); }); loglevel.info("check: res", res); if (Array.isArray(res) && res.length > 0) { if (this.confirmationStrategy === baseControllers.CONFIRMATION_STRATEGY.AUTO_APPROVE) loglevel.warn("Confirmation strategy auto approve is not allowed on Embed Mode, using Default instead."); return res; } // This would never happen, but just in case throw new Error("Login failed"); } catch (error) { loglevel.error("login failed", error); throw error; } finally { if (!isAlreadyFullScreen) this.communicationProvider.displayIframe({ isFull: false }); } } async loginWithSessionId(params) { if (!this.isInitialized) throw new Error("Call init() first"); if (!params.sessionId) throw new Error("sessionId is required"); try { const res = await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.LOGIN_WITH_SESSION_ID, params: [params.sessionId, params.sessionNamespace || ""] }); loglevel.info("check: res", res); if (res.success) { this.isPluginMode = true; if (this.confirmationStrategy === baseControllers.CONFIRMATION_STRATEGY.POPUP) loglevel.warn("Confirmation strategy popup is not allowed on Plugin Mode, using Default instead."); return res.success; } // This should never happen, but just in case throw new Error("Login failed"); } catch (error) { loglevel.error("login with session id failed", error); throw error; } } async logout() { if (!this.communicationProvider.isLoggedIn) throw new Error("Not logged in"); await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.LOGOUT, params: [] }); } async cleanUp() { var _this$communicationPr2; if ((_this$communicationPr2 = this.communicationProvider) !== null && _this$communicationPr2 !== void 0 && _this$communicationPr2.isLoggedIn) { await this.logout(); } this.clearInit(); } clearInit() { function isElement(element) { return element instanceof Element || element instanceof Document; } const walletIframe = window.document.getElementById(`walletIframe-${this.embedNonce}`); if (isElement(walletIframe)) { walletIframe.remove(); } this.isInitialized = false; } async getUserInfo() { const userInfoResponse = await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.USER_INFO, params: [] }); return userInfoResponse; } async showWalletConnectScanner(showWalletConnectParams) { const isShow = showWalletConnectParams ? showWalletConnectParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_WALLET_CONNECT, params: showWalletConnectParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } async showWalletUi(showWalletUiParams) { const isShow = showWalletUiParams ? showWalletUiParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_WALLET_UI, params: showWalletUiParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } async showFunding(showFundingParams) { const isShow = showFundingParams ? showFundingParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_FUNDING, params: showFundingParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } async showCheckout(showCheckoutParams) { const isShow = showCheckoutParams ? showCheckoutParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_CHECKOUT, params: showCheckoutParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } async showReceive(showReceiveParams) { const isShow = showReceiveParams ? showReceiveParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_RECEIVE, params: showReceiveParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } async showSwap(shoSwapParams) { const isShow = shoSwapParams ? shoSwapParams.show : true; this.communicationProvider.displayIframe({ isFull: isShow }); try { await this.communicationProvider.request({ method: baseControllers.COMMUNICATION_JRPC_METHODS.SHOW_SWAP, params: shoSwapParams }); } catch { this.communicationProvider.displayIframe({ isFull: false }); } } getConfirmationStrategyFinal(payload) { let confirmationStrategyFinal = this.confirmationStrategy; if (!Array.isArray(payload) && PROVIDER_UNSAFE_METHODS.has(payload.method) && this.confirmationStrategy !== baseControllers.CONFIRMATION_STRATEGY.MODAL && !this.isPluginMode) { confirmationStrategyFinal = baseControllers.CONFIRMATION_STRATEGY.POPUP; } return confirmationStrategyFinal; } getTheme(theme) { if (theme === auth.THEME_MODES.light) return "light"; if (theme === auth.THEME_MODES.dark) return "dark"; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } async setupWeb3(providerParams) { loglevel.info("setupWeb3 running"); // setup background connection const providerStream = new auth.BasePostMessageStream({ name: "embed_torus", target: "iframe_torus", targetWindow: providerParams.walletIframe.contentWindow, targetOrigin: new URL(providerParams.torusUrl).origin }); // We create another LocalMessageDuplexStream for communication between dapp <> iframe const communicationStream = new auth.BasePostMessageStream({ name: "embed_communication", target: "iframe_communication", targetWindow: providerParams.walletIframe.contentWindow, targetOrigin: new URL(providerParams.torusUrl).origin }); // compose the inPage provider const inPageProvider$1 = new inPageProvider(providerStream, {}); const communicationProvider$1 = new communicationProvider(communicationStream, {}, { buttonPosition: this.buttonPosition }); inPageProvider$1.tryWindowHandle = (payload, cb) => { const _payload = payload; const confirmationStrategyFinal = this.getConfirmationStrategyFinal(payload); const { allowWalletService } = communicationProvider$1; if (confirmationStrategyFinal === baseControllers.CONFIRMATION_STRATEGY.POPUP && allowWalletService) { const windowId = baseControllers.randomId(); communicationProvider$1.handleWindow(windowId, { target: "_blank", features: baseControllers.getPopupFeatures(baseControllers.FEATURES_CONFIRM_WINDOW), timeout: 500 }); // for inPageProvider methods sending windowId in request instead of params // as params might be positional. _payload.windowId = windowId; } inPageProvider$1.rpcEngine.handle(_payload, cb); }; communicationProvider$1.tryWindowHandle = (payload, cb) => { const _payload = payload; const confirmationStrategyFinal = this.getConfirmationStrategyFinal(payload); const { allowWalletService } = communicationProvider$1; if (confirmationStrategyFinal === baseControllers.CONFIRMATION_STRATEGY.POPUP && allowWalletService) { const windowId = baseControllers.randomId(); communicationProvider$1.handleWindow(windowId, { target: "_blank", features: baseControllers.getPopupFeatures(baseControllers.FEATURES_PROVIDER_CHANGE_WINDOW), // todo: are these features generic for all timeout: 500 }); // for communication methods sending window id in jrpc req params _payload.params.windowId = windowId; } communicationProvider$1.rpcEngine.handle(_payload, cb); }; // detect requestAccounts and pipe to enable for now const detectAccountRequestPrototypeModifier = m => { const originalMethod = inPageProvider$1[m]; // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore inPageProvider$1[m] = function providerFunc(request, cb) { const { method, params = [] } = request; if (method === constants.EVM_METHOD_TYPES.ETH_REQUEST_ACCOUNTS || method === constants.SOLANA_METHOD_TYPES.SOLANA_REQUEST_ACCOUNTS) { if (!cb) return self.login({ authConnection: params[0] }); self.login({ authConnection: params[0] }) // eslint-disable-next-line promise/no-callback-in-promise .then(res => cb(null, res)) // eslint-disable-next-line promise/no-callback-in-promise .catch(err => cb(err)); } return originalMethod.apply(this, [request, cb]); }; }; // Detects call to requestAccounts in request & sendAsync and passes to login detectAccountRequestPrototypeModifier("request"); detectAccountRequestPrototypeModifier("sendAsync"); detectAccountRequestPrototypeModifier("send"); const proxiedInPageProvider = new Proxy(inPageProvider$1, { // straight up lie that we deleted the property so that it doesn't // throw an error in strict mode deleteProperty: () => true }); const proxiedCommunicationProvider = new Proxy(communicationProvider$1, { // straight up lie that we deleted the property so that it doesn't // throw an error in strict mode deleteProperty: () => true }); this.provider = proxiedInPageProvider; this.communicationProvider = proxiedCommunicationProvider; loglevel.info("test setupWeb3 "); await Promise.all([inPageProvider$1.initializeState(), communicationProvider$1.initializeState(_objectSpread(_objectSpread({}, providerParams), {}, { torusIframeId: providerParams.walletIframe.id }))]); loglevel.debug("WsEmbed - injected provider"); } } exports.PROVIDER_UNSAFE_METHODS = PROVIDER_UNSAFE_METHODS; exports.default = WsEmbed; exports.version = version;