UNPKG

@web3auth/ws-embed

Version:

Embed script

327 lines (314 loc) 9.83 kB
import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { COMMUNICATION_JRPC_METHODS, COMMUNICATION_NOTIFICATIONS } from '@toruslabs/base-controllers'; import { EthereumProviderError } from '@web3auth/auth'; import BaseProvider from './baseProvider.js'; import { BUTTON_POSITION } from './interfaces.js'; import log from './loglevel.js'; import messages from './messages.js'; import PopupHandler from './PopupHandler.js'; /** * @param connectionStream - A Node.js duplex stream * @param opts - An options bag */ class CommunicationProvider extends BaseProvider { constructor(connectionStream, { maxEventListeners = 100, jsonRpcStreamName = "provider" }, state) { super(connectionStream, { maxEventListeners, jsonRpcStreamName }); // private state _defineProperty(this, "tryWindowHandle", void 0); _defineProperty(this, "windowRefs", void 0); _defineProperty(this, "iframeUrl", void 0); _defineProperty(this, "iframeId", void 0); this.state = _objectSpread(_objectSpread({}, CommunicationProvider.defaultState), state); // public state this.iframeUrl = ""; this.iframeId = ""; this.windowRefs = new Map(); // setup own event listeners // EIP-1193 connect this.on("connect", () => { this.state.isConnected = true; }); const notificationHandler = payload => { const { method, params } = payload; // create_window should never come here.. // we either pre-open from embed. // if it's blocked, we communicate down that it's blocked and we show full screen iframe and open from iframe if (method === COMMUNICATION_NOTIFICATIONS.IFRAME_STATUS) { const { isFullScreen, rid } = params; this.displayIframe({ isFull: isFullScreen, rid: rid }, true); } else if (method === COMMUNICATION_NOTIFICATIONS.CLOSE_WINDOW) { this.handleCloseWindow(params); } else if (method === COMMUNICATION_NOTIFICATIONS.USER_LOGGED_IN) { const { currentAuthConnection } = params; this.state.isLoggedIn = true; this.state.currentAuthConnection = currentAuthConnection; } else if (method === COMMUNICATION_NOTIFICATIONS.USER_LOGGED_OUT) { this.state.isLoggedIn = false; this.state.currentAuthConnection = null; this.displayIframe({ isFull: false }, true); } else if (method === COMMUNICATION_NOTIFICATIONS.TOGGLE_WIDGET_BUTTON) { const { show } = params; this.state.widgetVisibility = show; this.displayIframe(); } else if (method === COMMUNICATION_NOTIFICATIONS.ALLOW_WALLET_SERVICE) { const { allow } = params; this.state.allowWalletService = allow; } }; this.jsonRpcConnectionEvents.on("notification", notificationHandler); } get isLoggedIn() { return this.state.isLoggedIn; } get isIFrameFullScreen() { return this.state.isIFrameFullScreen; } get allowWalletService() { return this.state.allowWalletService; } /** * Returns whether the inPage provider is connected to Torus. */ isConnected() { return this.state.isConnected; } async initializeState(params) { try { const { torusUrl, torusIframeId } = params; this.iframeUrl = torusUrl; this.iframeId = torusIframeId; const { currentAuthConnection, isLoggedIn } = await this.request({ method: COMMUNICATION_JRPC_METHODS.GET_PROVIDER_STATE, params: [] }); // indicate that we've connected, for EIP-1193 compliance if (isLoggedIn) this.handleConnect(currentAuthConnection, isLoggedIn); } catch (error) { log.error("Web3Auth: Failed to get initial state. Please report this bug.", error); } finally { log.info("initialized communication state"); this.state.initialized = true; } } displayIframe({ isFull = false, rid = "" } = {}, walletRequest = false) { const style = {}; // set phase if (!isFull) { style.display = this.state.widgetVisibility ? "block" : "none"; style.height = this.state.widgetVisibility ? "70px" : "0px"; style.width = this.state.widgetVisibility ? "70px" : "0px"; switch (this.state.buttonPosition) { case BUTTON_POSITION.TOP_LEFT: style.top = "0px"; style.left = "0px"; style.right = "auto"; style.bottom = "auto"; break; case BUTTON_POSITION.TOP_RIGHT: style.top = "0px"; style.right = "0px"; style.left = "auto"; style.bottom = "auto"; break; case BUTTON_POSITION.BOTTOM_RIGHT: style.bottom = "0px"; style.right = "0px"; style.top = "auto"; style.left = "auto"; break; case BUTTON_POSITION.BOTTOM_LEFT: default: style.bottom = "0px"; style.left = "0px"; style.top = "auto"; style.right = "auto"; break; } } else { style.display = "block"; style.width = "100%"; style.height = "100%"; style.top = "0px"; style.right = "0px"; style.left = "0px"; style.bottom = "0px"; } const iframe = document.getElementById(this.iframeId); Object.assign(iframe.style, style); this.state.isIFrameFullScreen = isFull; if (!walletRequest) { this.request({ method: COMMUNICATION_JRPC_METHODS.IFRAME_STATUS, params: { isIFrameFullScreen: isFull, rid } }); } } /** * Scenarios: * - Login request or pre-open confirmation windows * We try to open here or send a rpc request to iframe that window is blocked. */ async handleWindow(windowId, { url, target, features, timeout } = {}) { const finalUrl = new URL(url || `${this.iframeUrl}/redirect?windowId=${windowId}&sessionNamespace=${window.location.hostname}`); const handledWindow = new PopupHandler({ url: finalUrl, target, features, timeout }); handledWindow.open(); if (!handledWindow.window) { this.displayIframe({ isFull: true }); this.request({ method: COMMUNICATION_JRPC_METHODS.WINDOW_BLOCKED, params: { windowId, finalUrl: finalUrl.href } }); return; } // Add to collection only if window is opened this.windowRefs.set(windowId, handledWindow); handledWindow.once("close", () => { // user closed the window this.windowRefs.delete(windowId); this.request({ method: COMMUNICATION_JRPC_METHODS.CLOSED_WINDOW, params: { windowId } }); }); } /** * Internal RPC method. Forwards requests to background via the RPC engine. * Also remap ids inbound and outbound */ rpcRequest(payload, callback) { const cb = callback; const _payload = payload; if (!Array.isArray(_payload)) { if (!_payload.jsonrpc) { _payload.jsonrpc = "2.0"; } } this.rpcEngine.handle(_payload, cb); } /** * When the provider becomes connected, updates internal state and emits * required events. Idempotent. * * @param currentAuthConnection - The auth connection * emits TorusInpageProvider#connect */ handleConnect(currentAuthConnection, isLoggedIn) { if (!this.state.isConnected) { this.state.isConnected = true; this.emit("connect", { currentAuthConnection, isLoggedIn }); log.debug(messages.info.connected(currentAuthConnection)); } } /** * When the provider becomes disconnected, updates internal state and emits * required events. Idempotent with respect to the isRecoverable parameter. * * Error codes per the CloseEvent status codes as required by EIP-1193: * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes * * @param isRecoverable - Whether the disconnection is recoverable. * @param errorMessage - A custom error message. * emits TorusInpageProvider#disconnect */ handleDisconnect(isRecoverable, errorMessage) { if (this.state.isConnected || !this.state.isPermanentlyDisconnected && !isRecoverable) { this.state.isConnected = false; let error; if (isRecoverable) { error = new EthereumProviderError(1013, // Try again later errorMessage || messages.errors.disconnected()); log.debug(error); } else { error = new EthereumProviderError(1011, // Internal error errorMessage || messages.errors.permanentlyDisconnected()); log.error(error); this.state.currentAuthConnection = null; this.state.isLoggedIn = false; this.state.widgetVisibility = false; this.state.isIFrameFullScreen = false; this.state.isPermanentlyDisconnected = true; } this.emit("disconnect", error); } } handleCloseWindow(params) { const { windowId } = params; if (this.windowRefs.has(windowId)) { this.windowRefs.get(windowId).close(); this.windowRefs.delete(windowId); } } } _defineProperty(CommunicationProvider, "defaultState", { buttonPosition: "bottom-left", currentAuthConnection: null, isIFrameFullScreen: false, widgetVisibility: false, initialized: false, isLoggedIn: false, isPermanentlyDisconnected: false, isConnected: false, hasEmittedConnection: false, allowWalletService: false }); export { CommunicationProvider as default };