@web3auth/ws-embed
Version:
Embed script
327 lines (314 loc) • 9.83 kB
JavaScript
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 };