UNPKG

@trilitech-umami/umami-embed

Version:

[WIP - not ready for production use] A simple embeddable Umami wallet

139 lines (138 loc) 5.18 kB
import { UmamiIframe } from "./UmamiIframe"; import { isFailedResponse, withTimeout } from "./utils"; import { Mutex, tryAcquire } from "async-mutex"; import { getIframeUrl } from "./iframeUrls"; const TIMEOUT = 50000; export class Messages { constructor(window, logger, useLocalEmbedIframe, network) { Object.defineProperty(this, "window", { enumerable: true, configurable: true, writable: true, value: window }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: logger }); Object.defineProperty(this, "useLocalEmbedIframe", { enumerable: true, configurable: true, writable: true, value: useLocalEmbedIframe }); Object.defineProperty(this, "network", { enumerable: true, configurable: true, writable: true, value: network }); Object.defineProperty(this, "iframe", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "actionMutex", { enumerable: true, configurable: true, writable: true, value: new Mutex() }); Object.defineProperty(this, "embedUrl", { enumerable: true, configurable: true, writable: true, value: "" }); this.iframe = new UmamiIframe(); this.embedUrl = getIframeUrl(this.useLocalEmbedIframe, this.network); } get isInitialized() { return this.iframe.isInitialized; } init(iframeParent) { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.init(iframeParent, this.embedUrl); return this.handleIFrameResponse("init_complete"); }); } destroy() { this.actionMutex.cancel(); this.iframe.destroy(); } setConfig(config) { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.request({ type: "config_request", config }); return this.handleIFrameResponse("config_response"); }); } login() { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.request({ type: "login_request" }); this.iframe.show(); return this.handleIFrameResponse("login_response"); }); } logout() { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.request({ type: "logout_request" }); return this.handleIFrameResponse("logout_response"); }); } send(operations) { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.request({ type: "operation_request", operations }); await this.handleIFrameResponse("computation_completed_response"); this.iframe.show(); return this.handleIFrameResponse("operation_response"); }); } sign(signingType, payload) { return tryAcquire(this.actionMutex).runExclusive(async () => { this.iframe.request({ type: "sign_request", signingType, payload, }); this.iframe.show(); return this.handleIFrameResponse("sign_response"); }); } // The promise is rejected on timeout or if the response is failed. // Response is only returned on success. handleIFrameResponse(expectedType) { let webResponseMessageListener; return withTimeout(() => new Promise((resolve, reject) => { webResponseMessageListener = (event) => { if (event.origin !== this.embedUrl) { return; } try { const data = JSON.parse(event.data); this.logger.info(`Received ${event.data} from ${event.origin}`); // Only process the response if it matches the awaited response type if (data.type === expectedType) { // TODO: remove secure data from logs. this.logger.info(`Processing ${event.data} from ${event.origin}`); if (isFailedResponse(data)) { reject(new Error(`${data.error}: ${data.errorMessage}`)); } else { resolve(data); } } } catch (e) { reject(`Error processing message from Umami iFrame: ${e}`); } }; this.window.addEventListener("message", webResponseMessageListener); }), TIMEOUT, `Wait for ${expectedType} has timed out`).finally(() => { this.iframe.hide(); this.window.removeEventListener("message", webResponseMessageListener); }); } }