@web3auth/ws-embed
Version:
Embed script
481 lines (470 loc) • 18.8 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { randomId, CONFIRMATION_STRATEGY, SETUP_COMPLETE, COMMUNICATION_JRPC_METHODS, getPopupFeatures, FEATURES_CONFIRM_WINDOW, FEATURES_PROVIDER_CHANGE_WINDOW, PROVIDER_JRPC_METHODS } from '@toruslabs/base-controllers';
import { THEME_MODES, getRpcPromiseCallback, BasePostMessageStream } from '@web3auth/auth';
import CommunicationProvider from './communicationProvider.js';
import { SOLANA_CHAIN_IDS, SOLANA_METHOD_TYPES, EVM_METHOD_TYPES } from './constants.js';
import { htmlToElement } from './embedUtils.js';
import TorusInPageProvider from './inPageProvider.js';
import { WS_EMBED_BUILD_ENV, BUTTON_POSITION } from './interfaces.js';
import log from './loglevel.js';
import getSiteMetadata from './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 = {
[WS_EMBED_BUILD_ENV.TESTING]: {
url: "https://develop-wallet.web3auth.io",
logLevel: "debug"
},
[WS_EMBED_BUILD_ENV.DEVELOPMENT]: {
url: "http://localhost:4050",
logLevel: "debug"
},
[WS_EMBED_BUILD_ENV.STAGING]: {
url: `https://staging-wallet.web3auth.io/${addedVersion}`,
logLevel: "debug"
},
[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) {
log.warn(error);
}
})();
new Set([COMMUNICATION_JRPC_METHODS.SET_PROVIDER]);
const PROVIDER_UNSAFE_METHODS = new Set([SOLANA_METHOD_TYPES.SEND_TRANSACTION, SOLANA_METHOD_TYPES.SIGN_MESSAGE, SOLANA_METHOD_TYPES.SIGN_TRANSACTION, SOLANA_METHOD_TYPES.SIGN_ALL_TRANSACTIONS, EVM_METHOD_TYPES.ETH_SIGN, EVM_METHOD_TYPES.ETH_SIGN_TYPED_DATA_V4, EVM_METHOD_TYPES.PERSONAL_SIGN, EVM_METHOD_TYPES.ETH_TRANSACTION, EVM_METHOD_TYPES.ADD_CHAIN, EVM_METHOD_TYPES.SWITCH_CHAIN, 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", 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 = WS_EMBED_BUILD_ENV.PRODUCTION,
enableLogging = false,
chains,
chainId,
walletUrls,
whiteLabel,
confirmationStrategy = 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];
log.info(torusUrl, "url loaded");
log.setDefaultLevel(logLevel);
if (enableLogging) log.enableAll();else log.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) || THEME_MODES.light);
// sandbox="allow-popups allow-scripts allow-same-origin"
// Iframe code
const walletIframe = 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) || BUTTON_POSITION.BOTTOM_LEFT;
const dappMetadata = await getSiteMetadata();
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 === 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(SOLANA_CHAIN_IDS).includes(this.provider.chainId);
const method = isSolana ? SOLANA_METHOD_TYPES.SOLANA_REQUEST_ACCOUNTS : EVM_METHOD_TYPES.ETH_REQUEST_ACCOUNTS;
this.provider.rpcRequest({
method,
params: [params.authConnection, params.login_hint]
}, getRpcPromiseCallback(resolve, reject));
});
log.info("check: res", res);
if (Array.isArray(res) && res.length > 0) {
if (this.confirmationStrategy === CONFIRMATION_STRATEGY.AUTO_APPROVE) log.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) {
log.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: COMMUNICATION_JRPC_METHODS.LOGIN_WITH_SESSION_ID,
params: [params.sessionId, params.sessionNamespace || ""]
});
log.info("check: res", res);
if (res.success) {
this.isPluginMode = true;
if (this.confirmationStrategy === CONFIRMATION_STRATEGY.POPUP) log.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) {
log.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: 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: 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: 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: 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: 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: 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: 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: 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 !== CONFIRMATION_STRATEGY.MODAL && !this.isPluginMode) {
confirmationStrategyFinal = CONFIRMATION_STRATEGY.POPUP;
}
return confirmationStrategyFinal;
}
getTheme(theme) {
if (theme === THEME_MODES.light) return "light";
if (theme === THEME_MODES.dark) return "dark";
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
async setupWeb3(providerParams) {
log.info("setupWeb3 running");
// setup background connection
const providerStream = new 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 BasePostMessageStream({
name: "embed_communication",
target: "iframe_communication",
targetWindow: providerParams.walletIframe.contentWindow,
targetOrigin: new URL(providerParams.torusUrl).origin
});
// compose the inPage provider
const inPageProvider = new TorusInPageProvider(providerStream, {});
const communicationProvider = new CommunicationProvider(communicationStream, {}, {
buttonPosition: this.buttonPosition
});
inPageProvider.tryWindowHandle = (payload, cb) => {
const _payload = payload;
const confirmationStrategyFinal = this.getConfirmationStrategyFinal(payload);
const {
allowWalletService
} = communicationProvider;
if (confirmationStrategyFinal === CONFIRMATION_STRATEGY.POPUP && allowWalletService) {
const windowId = randomId();
communicationProvider.handleWindow(windowId, {
target: "_blank",
features: getPopupFeatures(FEATURES_CONFIRM_WINDOW),
timeout: 500
});
// for inPageProvider methods sending windowId in request instead of params
// as params might be positional.
_payload.windowId = windowId;
}
inPageProvider.rpcEngine.handle(_payload, cb);
};
communicationProvider.tryWindowHandle = (payload, cb) => {
const _payload = payload;
const confirmationStrategyFinal = this.getConfirmationStrategyFinal(payload);
const {
allowWalletService
} = communicationProvider;
if (confirmationStrategyFinal === CONFIRMATION_STRATEGY.POPUP && allowWalletService) {
const windowId = randomId();
communicationProvider.handleWindow(windowId, {
target: "_blank",
features: getPopupFeatures(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.rpcEngine.handle(_payload, cb);
};
// detect requestAccounts and pipe to enable for now
const detectAccountRequestPrototypeModifier = m => {
const originalMethod = inPageProvider[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[m] = function providerFunc(request, cb) {
const {
method,
params = []
} = request;
if (method === EVM_METHOD_TYPES.ETH_REQUEST_ACCOUNTS || method === 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, {
// 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, {
// 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;
log.info("test setupWeb3 ");
await Promise.all([inPageProvider.initializeState(), communicationProvider.initializeState(_objectSpread(_objectSpread({}, providerParams), {}, {
torusIframeId: providerParams.walletIframe.id
}))]);
log.debug("WsEmbed - injected provider");
}
}
export { PROVIDER_UNSAFE_METHODS, WsEmbed as default, version };