UNPKG

@trezor/connect-web

Version:

High-level javascript interface for Trezor hardware wallet in web environment.

419 lines 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PopupManager = void 0; const tslib_1 = require("tslib"); const events_1 = tslib_1.__importDefault(require("events")); const version_1 = require("@trezor/connect/lib/data/version"); const events_2 = require("@trezor/connect/lib/events"); const urlUtils_1 = require("@trezor/connect/lib/utils/urlUtils"); const utils_1 = require("@trezor/utils"); const showPopupRequest_1 = require("./showPopupRequest"); const serviceworker_window_1 = require("../channels/serviceworker-window"); const window_window_1 = require("../channels/window-window"); const checkIfTabExists = (tabId) => new Promise(resolve => { if (!tabId) return resolve(false); function callback() { if (chrome.runtime.lastError) { resolve(false); } else { resolve(true); } } chrome.tabs.get(tabId, callback); }); const POPUP_REQUEST_TIMEOUT = 850; const POPUP_CLOSE_INTERVAL = 500; const POPUP_OPEN_TIMEOUT = 5000; class PopupManager extends events_1.default { popupWindow; settings; origin; locked = false; channel; channelIframe; handshakePromise; iframeHandshakePromise; popupPromise; requestTimeout; openTimeout; closeInterval; extensionTabId = 0; logger; constructor(settings, { logger }) { super(); this.settings = settings; this.origin = (0, urlUtils_1.getOrigin)(settings.popupSrc); this.logger = logger; if (this.isWebExtensionWithTab()) { this.channel = new serviceworker_window_1.ServiceWorkerWindowChannel({ name: 'trezor-connect', channel: { here: '@trezor/connect-webextension', peer: '@trezor/connect-content-script', }, logger, currentId: () => { if (this.popupWindow?.mode === 'tab') return this.popupWindow?.tab.id; }, legacyMode: !this.settings.useCoreInPopup, }); } else { this.channel = new window_window_1.WindowWindowChannel({ windowHere: window, windowPeer: () => { if (this.popupWindow?.mode === 'window') return this.popupWindow?.window; }, channel: { here: '@trezor/connect-web', peer: '@trezor/connect-popup', }, logger, origin: this.origin, legacyMode: !this.settings.useCoreInPopup, }); } if (!this.settings.useCoreInPopup) { this.iframeHandshakePromise = (0, utils_1.createDeferred)(events_2.IFRAME.LOADED); this.channelIframe = new window_window_1.WindowWindowChannel({ windowHere: window, windowPeer: () => window, channel: { here: '@trezor/connect-web', peer: '@trezor/connect-iframe', }, logger, origin: this.origin, }); this.channelIframe?.on('message', this.handleMessage.bind(this)); } if (this.settings.useCoreInPopup) { this.handshakePromise = (0, utils_1.createDeferred)(); this.channel.on('message', this.handleCoreMessage.bind(this)); return; } else if (this.isWebExtensionWithTab()) { this.channel.on('message', this.handleExtensionMessage.bind(this)); } else { this.channel.on('message', this.handleMessage.bind(this)); } this.channel.init(); } async request() { if (this.settings.useCoreInPopup && this.popupWindow?.mode === 'tab') { const currentPopupExists = await checkIfTabExists(this.popupWindow?.tab?.id); if (!currentPopupExists) { this.clear(); } } if (this.locked) { if (this.popupWindow?.mode === 'tab' && this.popupWindow.tab.id) { chrome.tabs.update(this.popupWindow.tab.id, { active: true }); } else if (this.popupWindow?.mode === 'window') { this.popupWindow.window.focus(); } return; } if (this.popupWindow && !this.locked) { this.close(); } const openFn = this.open.bind(this); this.locked = true; const timeout = this.settings.env === 'webextension' ? 1 : POPUP_REQUEST_TIMEOUT; this.requestTimeout = setTimeout(() => { this.requestTimeout = undefined; openFn(); }, timeout); } unlock() { this.locked = false; } open() { const src = this.settings.popupSrc; this.popupPromise = (0, utils_1.createDeferred)(events_2.POPUP.LOADED); const url = this.buildPopupUrl(src); this.openWrapper(url); this.closeInterval = setInterval(() => { if (!this.popupWindow) return; if (this.popupWindow.mode === 'tab' && this.popupWindow.tab.id) { chrome.tabs.get(this.popupWindow.tab.id, tab => { if (!tab) { this.emitClosed(); this.clear(); } }); } else if (this.popupWindow.mode === 'window' && this.popupWindow.window.closed) { this.clear(); this.emitClosed(); } }, POPUP_CLOSE_INTERVAL); if (this.settings.useCoreInPopup) { return; } this.openTimeout = setTimeout(() => { this.clear(); (0, showPopupRequest_1.showPopupRequest)(this.open.bind(this), () => { this.emitClosed(); }); }, POPUP_OPEN_TIMEOUT); } buildPopupUrl(src) { const params = new URLSearchParams(); params.set('version', version_1.VERSION); params.set('env', this.settings.env); if (this.settings.env === 'webextension' && chrome?.runtime?.id) { params.set('extension-id', chrome.runtime.id); params.set('cs-ver', version_1.CONTENT_SCRIPT_VERSION.toString()); } return src + '?' + params.toString(); } openWrapper(url) { if (this.isWebExtensionWithTab()) { chrome.windows.getCurrent(currentWindow => { this.logger.debug('opening popup. currentWindow: ', currentWindow); if (currentWindow.type !== 'normal') { chrome.windows.create({ url }, newWindow => { chrome.tabs.query({ windowId: newWindow?.id, active: true, }, tabs => { this.popupWindow = { mode: 'tab', tab: tabs[0] }; this.injectContentScript(tabs[0].id); }); }); } else { chrome.tabs.query({ currentWindow: true, active: true, }, tabs => { this.extensionTabId = tabs[0].id; chrome.tabs.create({ url, index: tabs[0].index + 1, }, tab => { this.popupWindow = { mode: 'tab', tab }; this.injectContentScript(tab.id); }); }); } }); } else { const windowResult = window.open(url, 'modal'); if (!windowResult) return; this.popupWindow = { mode: 'window', window: windowResult }; } if (!this.channel.isConnected) { this.channel.connect(); } } injectContentScript = (tabId) => { chrome.permissions.getAll(permissions => { if (permissions.permissions?.includes('scripting')) { (0, utils_1.scheduleAction)(() => chrome.scripting .executeScript({ target: { tabId }, func: () => { }, }) .then(() => { this.logger.debug('content script injected'); }) .catch(error => { this.logger.error('content script injection error', error); throw error; }), { attempts: new Array(3).fill({ timeout: 100 }) }); } else { } }); }; handleCoreMessage(message) { if (message.type === events_2.POPUP.BOOTSTRAP) { this.channel.init(); } else if (message.type === events_2.POPUP.LOADED) { this.handleMessage(message); this.channel.postMessage({ type: events_2.POPUP.INIT, payload: { settings: this.settings, useCore: true, }, }); } else if (message.type === events_2.POPUP.CORE_LOADED) { this.channel.postMessage({ type: events_2.POPUP.HANDSHAKE, payload: { settings: this.settings }, }); this.handshakePromise?.resolve(); } else if (message.type === events_2.POPUP.CLOSED) { this.emitClosed(); } else if (message.type === events_2.POPUP.CONTENT_SCRIPT_LOADED) { const { contentScriptVersion } = message.payload; if (contentScriptVersion !== version_1.CONTENT_SCRIPT_VERSION) { console.warn(`Content script version mismatch. Expected ${version_1.CONTENT_SCRIPT_VERSION}, got ${contentScriptVersion}`); } } else if (message.event === events_2.DEVICE_EVENT) { this.emit(events_2.DEVICE_EVENT, message); } } handleExtensionMessage(data) { if (data.type === events_2.POPUP.ERROR || data.type === events_2.POPUP.LOADED || data.type === events_2.POPUP.BOOTSTRAP) { this.handleMessage(data); } else if (data.type === events_2.POPUP.EXTENSION_USB_PERMISSIONS) { chrome.tabs.query({ currentWindow: true, active: true, }, tabs => { chrome.tabs.create({ url: 'trezor-usb-permissions.html', index: tabs[0].index + 1, }, _tab => { }); }); } else if (data.type === events_2.POPUP.CLOSE_WINDOW) { this.clear(); } } handleMessage(data) { if (data.type === events_2.IFRAME.LOADED) { this.iframeHandshakePromise?.resolve(data.payload); } else if (data.type === events_2.POPUP.BOOTSTRAP) { if (this.openTimeout) clearTimeout(this.openTimeout); } else if (data.type === events_2.POPUP.ERROR && this.popupWindow) { const errorMessage = data.payload && typeof data.payload.error === 'string' ? data.payload.error : null; this.emit(events_2.POPUP.CLOSED, errorMessage ? `Popup error: ${errorMessage}` : null); this.clear(); } else if (data.type === events_2.POPUP.LOADED) { if (this.openTimeout) clearTimeout(this.openTimeout); if (this.popupPromise) { this.popupPromise.resolve(); this.popupPromise = undefined; } this.iframeHandshakePromise?.promise.then(payload => { this.channel.postMessage({ type: events_2.POPUP.INIT, payload: { ...payload, settings: this.settings, }, }); }); } else if (data.type === events_2.POPUP.CANCEL_POPUP_REQUEST) { clearTimeout(this.requestTimeout); if (this.popupPromise) { this.close(); } this.unlock(); } else if (data.type === events_2.UI.CLOSE_UI_WINDOW) { this.clear(false); } } clear(focus = true) { this.locked = false; this.popupPromise = undefined; this.handshakePromise = (0, utils_1.createDeferred)(); if (this.channel) { this.channel.disconnect(); } if (this.requestTimeout) { clearTimeout(this.requestTimeout); this.requestTimeout = undefined; } if (this.openTimeout) { clearTimeout(this.openTimeout); this.openTimeout = undefined; } if (this.closeInterval) { clearInterval(this.closeInterval); this.closeInterval = undefined; } if (focus && this.extensionTabId) { chrome.tabs.update(this.extensionTabId, { active: true }); this.extensionTabId = 0; } } close() { if (!this.popupWindow) return; this.logger.debug('closing popup'); if (this.popupWindow.mode === 'tab') { let _e = chrome.runtime.lastError; if (this.popupWindow.tab.id) { chrome.tabs.remove(this.popupWindow.tab.id, () => { _e = chrome.runtime.lastError; if (_e) { this.logger.error('closed with error', _e); } }); } } else if (this.popupWindow.mode === 'window') { this.popupWindow.window.close(); } this.popupWindow = undefined; if (this.settings?.useCoreInPopup) { this.channel.clear(); } } async postMessage(message) { if (!this.popupWindow && message.type !== events_2.UI.REQUEST_UI_WINDOW && this.openTimeout) { this.clear(); (0, showPopupRequest_1.showPopupRequest)(this.open.bind(this), () => { this.emitClosed(); }); return; } if (this.popupPromise) { await this.popupPromise.promise; } if (this.popupWindow?.mode === 'window') { this.popupWindow.window.postMessage(message, this.origin); } else if (this.popupWindow?.mode === 'tab') { this.channel.postMessage(message); } } isWebExtensionWithTab() { return (this.settings?.env === 'webextension' && typeof chrome !== 'undefined' && typeof chrome?.tabs !== 'undefined'); } emitClosed() { if (this.settings?.useCoreInPopup) { this.channel.resolveMessagePromises({ code: 'Method_Interrupted', error: events_2.POPUP.CLOSED, }); } this.emit(events_2.POPUP.CLOSED); } } exports.PopupManager = PopupManager; //# sourceMappingURL=index.js.map