UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

834 lines 36.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.initCoreState = exports.Core = void 0; const tslib_1 = require("tslib"); const events_1 = tslib_1.__importDefault(require("events")); const connect_common_1 = require("@trezor/connect-common"); const transport_1 = require("@trezor/transport"); const utils_1 = require("@trezor/utils"); const constants_1 = require("../constants"); const method_1 = require("./method"); const onCallFirmwareUpdate_1 = require("./onCallFirmwareUpdate"); const BlockchainLink_1 = require("../backend/BlockchainLink"); const DataManager_1 = require("../data/DataManager"); const analyticsInfo_1 = require("../data/analyticsInfo"); const connectSettings_1 = require("../data/connectSettings"); const DeviceList_1 = require("../device/DeviceList"); const StateStorage_1 = require("../device/StateStorage"); const workflows = tslib_1.__importStar(require("../device/workflow")); const events_2 = require("../events"); const debug_1 = require("../utils/debug"); const interactionTimeout_1 = require("../utils/interactionTimeout"); const popupPromiseManager_1 = require("../utils/popupPromiseManager"); const uiPromiseManager_1 = require("../utils/uiPromiseManager"); const _log = (0, debug_1.initLog)('Core'); const waitForPopup = ({ popupPromise, sendCoreMessage }) => { sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_UI_WINDOW)); return popupPromise.wait(); }; const startInteractionTimeout = (context) => context.interactionTimeout.start(() => { onPopupClosed(context, 'Interaction timeout'); }); const initDevice = async (context, methodCallDevice) => { const { uiPromises, deviceList, sendCoreMessage } = context; (0, DeviceList_1.assertDeviceListConnected)(deviceList); const isWebUsb = deviceList.getActiveTransports().some(t => t.type === 'WebUsbTransport'); let device; let showDeviceSelection = isWebUsb; const isUsingPopup = DataManager_1.DataManager.getSettings('popup'); const origin = DataManager_1.DataManager.getSettings('origin'); const useCoreInPopup = DataManager_1.DataManager.getSettings('useCoreInPopup'); const { preferredDevice } = connect_common_1.storage.load().origin[origin] || {}; const preferredDeviceInList = preferredDevice?.state && deviceList.getDeviceByStaticState(preferredDevice.state); if (methodCallDevice?.state?.staticSessionId) { device = deviceList.getDeviceByStaticState(methodCallDevice.state.staticSessionId); } else if (methodCallDevice?.path) { device = deviceList.getDeviceByPath(methodCallDevice.path); } if (preferredDevice && !device) { if (preferredDeviceInList) { device = preferredDeviceInList; } else { connect_common_1.storage.save(store => { store.origin[origin] = { ...store.origin[origin], preferredDevice: undefined }; return store; }); } } if (device) { showDeviceSelection = device.isUnreadable() || (device.isUnacquired() && !!isUsingPopup); } else { const onlyDevice = deviceList.getOnlyDevice(); if (onlyDevice && (!isWebUsb || !isUsingPopup)) { device = onlyDevice; showDeviceSelection = device.isUnreadable() || device.isUnacquired() || !!useCoreInPopup; } else { showDeviceSelection = true; } } if (showDeviceSelection) { uiPromises.create(events_2.UI.RECEIVE_DEVICE); await waitForPopup(context); (0, DeviceList_1.assertDeviceListConnected)(deviceList); const onlyDevice = deviceList.getOnlyDevice(); if (onlyDevice && !onlyDevice.isUnreadable() && !onlyDevice.isUnacquired() && !isWebUsb && !useCoreInPopup) { device = onlyDevice; } else { sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.SELECT_DEVICE, { webusb: isWebUsb, devices: deviceList.getAllDevices().map(d => d.toMessageObject()), })); if (uiPromises.exists(events_2.UI.RECEIVE_DEVICE)) { const { payload } = await uiPromises.get(events_2.UI.RECEIVE_DEVICE); if (payload.remember) { const { label, path, state } = payload.device; connect_common_1.storage.save(store => { store.origin[origin] = { ...store.origin[origin], preferredDevice: { label, path, state }, }; return store; }); } device = deviceList.getDeviceByPath(payload.device.path); } } } else if (uiPromises.exists(events_2.UI.RECEIVE_DEVICE)) { await uiPromises.get(events_2.UI.RECEIVE_DEVICE); } if (!device) { throw constants_1.ERRORS.TypedError('Device_NotFound'); } return device; }; const inner = async (context, method, device) => { const { uiPromises, sendCoreMessage } = context; const trustedHost = DataManager_1.DataManager.getSettings('trustedHost'); const isUsingPopup = DataManager_1.DataManager.getSettings('popup') ?? false; const firmwareException = method.checkFirmwareRange(); if (firmwareException) { if (isUsingPopup) { if (firmwareException === events_2.UI.FIRMWARE_NOT_COMPATIBLE) { await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_CONFIRMATION, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.FIRMWARE_NOT_COMPATIBLE, device.toMessageObject())); const uiResp = await uiPromise.promise; if (!uiResp.payload) { throw constants_1.ERRORS.TypedError('Method_PermissionsNotGranted'); } } else { await waitForPopup(context); sendCoreMessage((0, events_2.createUiMessage)(firmwareException, device.toMessageObject())); await uiPromises.create(events_2.DEVICE.DISCONNECT, device).promise; return Promise.reject(constants_1.ERRORS.TypedError('Method_Cancel')); } } else { return Promise.reject(constants_1.ERRORS.TypedError('Device_FwException', firmwareException)); } } const unexpectedMode = device.hasUnexpectedMode(method.allowDeviceMode, method.requireDeviceMode); if (unexpectedMode) { if (isUsingPopup) { await waitForPopup(context); sendCoreMessage((0, events_2.createUiMessage)(unexpectedMode, device.toMessageObject())); await uiPromises.create(events_2.DEVICE.DISCONNECT, device).promise; return Promise.reject(constants_1.ERRORS.TypedError('Device_ModeException', unexpectedMode)); } return Promise.reject(constants_1.ERRORS.TypedError('Device_ModeException', unexpectedMode)); } method.checkDeviceCapability(); method.checkPermissions({ origin: DataManager_1.DataManager.getSettings('origin') }); if (!trustedHost && method.requiredPermissions.length > 0) { await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_PERMISSION, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_PERMISSION, { permissions: method.requiredPermissions, device: device.toMessageObject(), })); const { granted, remember } = await uiPromise.promise.then(({ payload }) => payload); if (granted) { method.savePermissions(!remember, { origin: DataManager_1.DataManager.getSettings('origin') }); } else { return Promise.reject(constants_1.ERRORS.TypedError('Method_PermissionsNotGranted')); } } const deviceNeedsBackup = device.features.backup_availability === 'Required'; if (deviceNeedsBackup) { if (method.noBackupConfirmationMode === 'always' || (method.noBackupConfirmationMode === 'popup-only' && isUsingPopup)) { await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_CONFIRMATION, device); sendCoreMessage((0, analyticsInfo_1.enhanceMessageWithAnalytics)((0, events_2.createUiMessage)(events_2.UI.REQUEST_CONFIRMATION, { view: 'no-backup', }), { device: device.toMessageObject() })); const permitted = await uiPromise.promise.then(({ payload }) => payload); if (!permitted) { return Promise.reject(constants_1.ERRORS.TypedError('Method_PermissionsNotGranted')); } } await waitForPopup(context); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.DEVICE_NEEDS_BACKUP, device.toMessageObject())); } if (device.firmwareStatus === 'outdated') { await waitForPopup(context); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.FIRMWARE_OUTDATED, device.toMessageObject())); } if (!trustedHost) { const requestConfirmation = method.confirmation; if (requestConfirmation) { await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_CONFIRMATION, device); sendCoreMessage((0, analyticsInfo_1.enhanceMessageWithAnalytics)((0, events_2.createUiMessage)(events_2.UI.REQUEST_CONFIRMATION, requestConfirmation), { device: device.toMessageObject() })); const confirmed = await uiPromise.promise.then(({ payload }) => payload); if (!confirmed) { return Promise.reject(constants_1.ERRORS.TypedError('Method_Cancel')); } } } const workflowCtx = { device, method, signal: context.signal, }; await workflows.validateState(workflowCtx); if (method.useUi) { await waitForPopup(context); } else { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); } try { const response = await method.run(); return (0, events_2.createResponseMessage)(method.responseID, true, response, device); } catch (error) { return Promise.reject(error); } }; const onCall = async (context, message) => { if (!message.id || !message.payload || message.type !== events_2.IFRAME.CALL) { throw constants_1.ERRORS.TypedError('Method_InvalidParameter', 'onCall: message.id or message.payload is missing'); } const { uiPromises, callMethods, methodSynchronize, resolveWaitForFirstMethod, sendCoreMessage, } = context; const responseID = message.id; let method; try { method = await methodSynchronize(async () => { _log.debug('loading method...'); const method2 = await (0, method_1.getMethod)(message); _log.debug('method selected', method2.name); method2.postMessage = sendCoreMessage; method2.createUiPromise = uiPromises.create; method2.init(); await method2.initAsync?.(); return method2; }); resolveWaitForFirstMethod(); callMethods.push(method); } catch (error) { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); sendCoreMessage((0, events_2.createResponseMessage)(responseID, false, { error })); return Promise.resolve(); } if (method.payload.__info) { const response = await method.getMethodInfo(); sendCoreMessage((0, events_2.createResponseMessage)(method.responseID, true, response)); return Promise.resolve(); } if (!method.useDevice) { try { if (method.useUi) { await waitForPopup(context); } else { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); } const response = await method.run(); sendCoreMessage((0, events_2.createResponseMessage)(method.responseID, true, response)); } catch (error) { sendCoreMessage((0, events_2.createResponseMessage)(method.responseID, false, { error })); } return Promise.resolve(); } if (method.isManagementRestricted({ origin: DataManager_1.DataManager.getSettings('origin') })) { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); sendCoreMessage((0, events_2.createResponseMessage)(responseID, false, { error: constants_1.ERRORS.TypedError('Method_NotAllowed'), })); return Promise.resolve(); } return await onCallDevice(context, message, method); }; const onCallDevice = async (context, message, method) => { const { deviceList, callMethods, sendCoreMessage } = context; const responseID = message.id; const { origin, env, useCoreInPopup, transports } = DataManager_1.DataManager.getSettings(); if (!deviceList.isConnected() && !deviceList.pendingConnection()) { deviceList.init({ transports }); } await deviceList.pendingConnection(); const shouldRetry = ['web', 'webextension'].includes(env); let tempDevice; while (!tempDevice) { try { tempDevice = await initDevice(context, message.payload.device); } catch (error) { if (error.code === 'Transport_Missing') { await waitForPopup(context); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.TRANSPORT)); if (deviceList.pendingConnection() && shouldRetry) { while (deviceList.pendingConnection()) { await deviceList.pendingConnection(); } continue; } } else { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); } sendCoreMessage((0, events_2.createResponseMessage)(responseID, false, { error })); throw error; } } const device = tempDevice; method.setDevice(device); const previousCall = callMethods.filter(call => call && call !== method && call.device?.getUniquePath() === method.device.getUniquePath()); if (previousCall.length > 0 && method.overridePreviousCall) { previousCall.forEach(call => { call.overridden = true; }); const overrideError = constants_1.ERRORS.TypedError('Method_Override'); await device.interrupt(overrideError); if (method.overridden) { sendCoreMessage((0, events_2.createResponseMessage)(method.responseID, false, { error: overrideError })); throw overrideError; } } else if (device.currentRun) { if (device.isUnacquired()) { await device.currentRun; } else { sendCoreMessage((0, events_2.createResponseMessage)(responseID, false, { error: constants_1.ERRORS.TypedError('Device_CallInProgress'), })); throw constants_1.ERRORS.TypedError('Device_CallInProgress'); } } device.setInstance(message.payload.device?.instance); if (method.hasExpectedDeviceState) { device.setState(method.deviceState); } registerDeviceEvents(context, method)(device); if (useCoreInPopup && env === 'webextension' && origin) { device.initStorage(new StateStorage_1.WebextensionStateStorage(origin)); } let messageResponse; try { const innerAction = () => inner(context, method, device).then(response => { messageResponse = response; }); await device.run(innerAction, { keepSession: method.keepSession, skipFinalReload: method.skipFinalReload, useCardanoDerivation: method.useCardanoDerivation, }); } catch (error) { if (error.cause) { _log.debug('device.run error caught, caused by:', error.cause); } if (error.code === 'Device_Disconnected') { deviceList.addAuthPenalty(device); } if (method) { if (deviceList.isConnected() && error.message === transport_1.TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS) { await deviceList.enumerate(); } messageResponse = (0, events_2.createResponseMessage)(method.responseID, false, { error }); } } finally { if (method.keepSession && method.deviceState && method.deviceState.sessionId !== device.getState()?.sessionId) { sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.CHANGED, device.toMessageObject())); } const response = messageResponse; if (response) { device.eventNames().forEach(e => device.removeAllListeners(e)); if (useCoreInPopup) { sendCoreMessage(response); } closePopup(context); cleanup(context); if (method) { method.dispose(); } if (response.success) { deviceList.removeAuthPenalty(device); } if (!useCoreInPopup) { sendCoreMessage(response); } } } }; const cleanup = ({ uiPromises, popupPromise, interactionTimeout }) => { popupPromise.clear(); uiPromises.clear(); interactionTimeout.stop(); _log.debug('Cleanup...'); }; const closePopup = ({ popupPromise, sendCoreMessage }) => { if (popupPromise.isWaiting()) { sendCoreMessage((0, events_2.createPopupMessage)(events_2.POPUP.CANCEL_POPUP_REQUEST)); } sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.CLOSE_UI_WINDOW)); }; const onDeviceButtonHandler = (device, context, method) => async ({ payload: request }) => { const { sendCoreMessage } = context; const addressRequest = request.code === 'ButtonRequest_Address'; if (!addressRequest || (addressRequest && method?.useUi)) { await waitForPopup(context); } const data = typeof method?.getButtonRequestData === 'function' && request.code ? method?.getButtonRequestData(request.code, request.name) : undefined; startInteractionTimeout(context); sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.BUTTON, { ...request, device: device.toMessageObject() })); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_BUTTON, { ...request, device: device.toMessageObject(), data, })); if (addressRequest && !method?.useUi) { sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.ADDRESS_VALIDATION, data)); } }; const onDevicePinHandler = (device, context) => async ({ type, callback }) => { const { uiPromises, sendCoreMessage } = context; await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_PIN, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_PIN, { device: device.toMessageObject(), type })); try { const uiResp = await uiPromise.promise; if (uiResp.payload == null) { callback({ success: false, error: new Error(`${events_2.UI.RECEIVE_PIN} missing payload`) }); } else { callback({ success: true, payload: uiResp.payload }); } } catch (error) { callback({ success: false, error }); } }; const onDeviceWordHandler = (device, context) => async ({ type, callback }) => { const { uiPromises, sendCoreMessage } = context; await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_WORD, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_WORD, { device: device.toMessageObject(), type })); try { const uiResp = await uiPromise.promise; if (uiResp.payload == null) { callback({ success: false, error: new Error(`${events_2.UI.RECEIVE_WORD} missing payload`), }); } else { callback({ success: true, payload: uiResp.payload }); } } catch (error) { callback({ success: false, error }); } }; const onDevicePassphraseHandler = (device, context) => async ({ callback }) => { const { uiPromises, sendCoreMessage } = context; await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_PASSPHRASE, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_PASSPHRASE, { device: device.toMessageObject() })); try { const uiResp = await uiPromise.promise; if (uiResp.payload == null) { callback({ success: false, error: new Error(`${events_2.UI.RECEIVE_PASSPHRASE} missing payload`), }); } else { callback({ success: true, payload: uiResp.payload }); } } catch (error) { callback({ success: false, error }); } }; const onEmptyPassphraseHandler = () => ({ callback }) => { callback({ success: true, payload: { value: '' } }); }; const onThpPairingHandler = (device, context) => async ({ callback, payload }) => { const { uiPromises, sendCoreMessage } = context; await waitForPopup(context); const uiPromise = uiPromises.create(events_2.UI.RECEIVE_THP_PAIRING_TAG, device); sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_THP_PAIRING, { device: device.toMessageObject(), ...payload, })); try { const uiResp = await uiPromise.promise; if (uiResp.payload == null) { callback({ success: false, error: new Error(`${events_2.UI.RECEIVE_THP_PAIRING_TAG} missing payload`), }); } else { callback({ success: true, payload: uiResp.payload }); } } catch (error) { callback({ success: false, error }); } }; const onThpCredentialsChangedHandler = (device, context) => (payload) => { const { sendCoreMessage } = context; sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.THP_CREDENTIALS_CHANGED, { device: device.toMessageObject(), ...payload, })); }; const registerDeviceEvents = (context, method) => (device) => { device.removeAllListeners(); device.on(events_2.DEVICE.BUTTON, onDeviceButtonHandler(device, context, method)); device.on(events_2.DEVICE.PIN, onDevicePinHandler(device, context)); device.on(events_2.DEVICE.WORD, onDeviceWordHandler(device, context)); device.on(events_2.DEVICE.PASSPHRASE, (method?.useEmptyPassphrase ? onEmptyPassphraseHandler : onDevicePassphraseHandler)(device, context)); device.on(events_2.DEVICE.PASSPHRASE_ON_DEVICE, () => { context.sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_PASSPHRASE_ON_DEVICE, { device: device.toMessageObject(), })); }); device.on(events_2.DEVICE.FIRMWARE_VERSION_CHANGED, payload => { context.sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.FIRMWARE_VERSION_CHANGED, payload)); }); device.on(events_2.DEVICE.THP_PAIRING, onThpPairingHandler(device, context)); device.on(events_2.DEVICE.THP_CREDENTIALS_CHANGED, onThpCredentialsChangedHandler(device, context)); }; const onPopupClosed = (context, customErrorMessage) => { const { uiPromises, popupPromise, deviceList, callMethods, resetWaitForFirstMethod, sendCoreMessage, } = context; const error = customErrorMessage ? constants_1.ERRORS.TypedError('Method_Cancel', customErrorMessage) : constants_1.ERRORS.TypedError('Method_Interrupted'); if (deviceList.isConnected() && deviceList.getDeviceCount() > 0) { deviceList.getAllDevices().forEach(d => { if (d.isUsedHere()) { d.interrupt(error); } else { const success = uiPromises.resolve({ type: events_2.DEVICE.DISCONNECT, payload: undefined }); if (!success) { callMethods.forEach(m => { sendCoreMessage((0, events_2.createResponseMessage)(m.responseID, false, { error })); }); callMethods.splice(0, callMethods.length); resetWaitForFirstMethod(); } } }); } else { uiPromises.rejectAll(error); popupPromise.reject(error); } cleanup(context); }; const handleDeviceSelectionChanges = (context, interruptDevice) => { const { uiPromises, deviceList, sendCoreMessage } = context; const promiseExists = uiPromises.exists(events_2.UI.RECEIVE_DEVICE); if (promiseExists && deviceList.isConnected()) { const onlyDevice = deviceList.getOnlyDevice(); const isWebUsb = deviceList.getActiveTransports().some(t => t.type === 'WebUsbTransport'); if (onlyDevice && !isWebUsb) { uiPromises.resolve({ type: events_2.UI.RECEIVE_DEVICE, payload: { device: onlyDevice.toMessageObject() }, }); } else { sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.SELECT_DEVICE, { webusb: isWebUsb, devices: deviceList.getAllDevices().map(d => d.toMessageObject()), })); } } if (interruptDevice) { const { path } = interruptDevice; const shouldClosePopup = uiPromises.disconnected(path); if (shouldClosePopup) { closePopup(context); cleanup(context); } } }; const initDeviceList = (context) => { const { deviceList, sendCoreMessage } = context; deviceList.on(events_2.DEVICE.CONNECT, device => { handleDeviceSelectionChanges(context); sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.CONNECT, device.toMessageObject())); }); deviceList.on(events_2.DEVICE.CONNECT_UNACQUIRED, device => { handleDeviceSelectionChanges(context); sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.CONNECT_UNACQUIRED, device.toMessageObject())); }); deviceList.on(events_2.DEVICE.DISCONNECT, device => { handleDeviceSelectionChanges(context); sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.DISCONNECT, device.toMessageObject())); }); deviceList.on(events_2.DEVICE.CHANGED, device => { sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.CHANGED, device.toMessageObject())); }); deviceList.on(transport_1.TRANSPORT.START, transport => sendCoreMessage((0, events_2.createTransportMessage)(transport_1.TRANSPORT.START, transport))); deviceList.on(transport_1.TRANSPORT.ERROR, error => { _log.warn('TRANSPORT.ERROR', error.error); sendCoreMessage((0, events_2.createTransportMessage)(transport_1.TRANSPORT.ERROR, error)); }); }; class Core extends events_1.default { abortController = new AbortController(); callMethods = []; popupPromise = (0, popupPromiseManager_1.createPopupPromiseManager)(); methodSynchronize = (0, utils_1.getSynchronize)(); uiPromises = (0, uiPromiseManager_1.createUiPromiseManager)(() => startInteractionTimeout(this.getCoreContext())); waitForFirstMethod = (0, utils_1.createDeferred)(); _interactionTimeout; get interactionTimeout() { return this._interactionTimeout ?? (0, utils_1.throwError)('Core not initialized: interactionTimeout'); } _deviceList; get deviceList() { return this._deviceList ?? (0, utils_1.throwError)('Core not initialized: deviceList'); } sendCoreMessage(message) { if (message.event === events_2.RESPONSE_EVENT) { const index = this.callMethods.findIndex(call => call && call.responseID === message.id); if (index >= 0) { this.callMethods.splice(index, 1); if (this.callMethods.length === 0) { this.waitForFirstMethod = (0, utils_1.createDeferred)(); } } } this.emit(events_2.CORE_EVENT, message); } getCoreContext() { return { signal: this.abortController.signal, uiPromises: this.uiPromises, popupPromise: this.popupPromise, interactionTimeout: this.interactionTimeout, deviceList: this.deviceList, callMethods: this.callMethods, methodSynchronize: this.methodSynchronize, sendCoreMessage: this.sendCoreMessage.bind(this), resetWaitForFirstMethod: () => { this.waitForFirstMethod = (0, utils_1.createDeferred)(); }, resolveWaitForFirstMethod: () => { this.waitForFirstMethod.resolve(); }, }; } handleMessage(message) { _log.debug('handleMessage', message.type); switch (message.type) { case events_2.POPUP.HANDSHAKE: this.popupPromise.resolve(); break; case events_2.POPUP.CLOSED: this.popupPromise.clear(); onPopupClosed(this.getCoreContext(), message.payload ? message.payload.error : null); break; case transport_1.TRANSPORT.DISABLE_WEBUSB: { const settings = DataManager_1.DataManager.getSettings(); const transports = settings.transports?.filter(t => t !== 'WebUsbTransport'); if (transports && !transports.includes('BridgeTransport')) { transports.unshift('BridgeTransport'); } settings.transports = transports; resetTransports(this.getCoreContext()); break; } case transport_1.TRANSPORT.SET_TRANSPORTS: DataManager_1.DataManager.getSettings().transports = message.payload.transports; resetTransports(this.getCoreContext()); break; case transport_1.TRANSPORT.REQUEST_DEVICE: if (this.deviceList.isConnected()) { this.deviceList.enumerate(); } break; case transport_1.TRANSPORT.GET_INFO: this.sendCoreMessage((0, events_2.createResponseMessage)(message.id, true, this.getActiveTransports())); break; case events_2.UI.RECEIVE_DEVICE: case events_2.UI.RECEIVE_CONFIRMATION: case events_2.UI.RECEIVE_PERMISSION: case events_2.UI.RECEIVE_PIN: case events_2.UI.RECEIVE_PASSPHRASE: case events_2.UI.INVALID_PASSPHRASE_ACTION: case events_2.UI.RECEIVE_THP_PAIRING_TAG: case events_2.UI.RECEIVE_ACCOUNT: case events_2.UI.RECEIVE_FEE: case events_2.UI.RECEIVE_WORD: case events_2.UI.LOGIN_CHALLENGE_RESPONSE: this.uiPromises.resolve(message); break; case events_2.UI.RECEIVE_FIRMWARE: { const localFirmwares = message.payload && (0, connectSettings_1.parseLocalFirmwares)(message.payload); if (localFirmwares) { DataManager_1.DataManager.setLocalFirmwares(localFirmwares); } break; } case events_2.IFRAME.CALL: if (message.payload.method === 'firmwareUpdate') { (0, DeviceList_1.assertDeviceListConnected)(this.deviceList); const coreContext = this.getCoreContext(); (0, onCallFirmwareUpdate_1.onCallFirmwareUpdate)({ params: message.payload, context: { deviceList: this.deviceList, postMessage: this.sendCoreMessage.bind(this), initDevice: path => initDevice(coreContext, { path }), log: _log, abortSignal: this.abortController.signal, registerEvents: registerDeviceEvents(coreContext), }, }) .then(payload => { this.sendCoreMessage((0, events_2.createResponseMessage)(message.id, true, payload)); }) .catch(error => { this.sendCoreMessage((0, events_2.createResponseMessage)(message.id, false, { error })); _log.error('onCallFirmwareUpdate', error); }); } else { onCall(this.getCoreContext(), message).catch(error => { _log.error('onCall', error); }); } } } dispose() { (0, BlockchainLink_1.dispose)(); this.removeAllListeners(); this.abortController.abort(); this.deviceList.dispose(); } async getCurrentMethod() { await this.waitForFirstMethod.promise; return await this.methodSynchronize(() => this.callMethods[0]); } getActiveTransports() { if (this.deviceList.isConnected()) { return this.deviceList.getActiveTransports(); } } enumerate() { if (this.deviceList.isConnected()) { this.deviceList.enumerate(); } } async init(settings, onCoreEvent, logWriterFactory) { if (logWriterFactory) { (0, debug_1.setLogWriter)(logWriterFactory); } const throttlePromise = (0, utils_1.createDeferred)(); throttlePromise.promise.catch(() => { }); const onCoreEventThrottled = (message) => throttlePromise.promise.then(() => onCoreEvent(message)); try { await DataManager_1.DataManager.load(settings); const localFirmwares = settings.localFirmwares && (0, connectSettings_1.parseLocalFirmwares)(settings.localFirmwares); if (localFirmwares) { DataManager_1.DataManager.setLocalFirmwares(localFirmwares); } const { debug, priority, manifest } = DataManager_1.DataManager.getSettings(); const messages = DataManager_1.DataManager.getProtobufMessages(); (0, debug_1.enableLog)(debug); this._interactionTimeout = new interactionTimeout_1.InteractionTimeout(settings.popup ? settings.interactionTimeout : 0); this._deviceList = new DeviceList_1.DeviceList({ debug, messages, priority, manifest, }); initDeviceList(this.getCoreContext()); this.on(events_2.CORE_EVENT, onCoreEventThrottled); } catch (error) { _log.error('init', error); throttlePromise.reject(error); throw error; } const { transports, pendingTransportEvent, transportReconnect, coreMode } = DataManager_1.DataManager.getSettings(); try { this.deviceList.init({ transports, pendingTransportEvent, transportReconnect }); } catch (error) { this.sendCoreMessage((0, events_2.createTransportMessage)(transport_1.TRANSPORT.ERROR, { error })); throttlePromise.reject(error); throw error; } if (!transportReconnect || coreMode === 'auto') { await this.deviceList.pendingConnection(); } this.on(events_2.CORE_EVENT, onCoreEvent); this.off(events_2.CORE_EVENT, onCoreEventThrottled); setTimeout(throttlePromise.resolve, 0); } } exports.Core = Core; const resetTransports = async ({ deviceList, sendCoreMessage }) => { const { transports, pendingTransportEvent, transportReconnect } = DataManager_1.DataManager.getSettings(); try { await deviceList.init({ transports, pendingTransportEvent, transportReconnect }); } catch (error) { sendCoreMessage((0, events_2.createTransportMessage)(transport_1.TRANSPORT.ERROR, { error })); } }; const initCore = async (...params) => { const core = new Core(); await core.init(...params); return core; }; const disposeCore = (core) => { core.dispose(); }; const initCoreState = () => (0, utils_1.createLazy)(initCore, disposeCore); exports.initCoreState = initCoreState; //# sourceMappingURL=index.js.map