@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
834 lines • 36.9 kB
JavaScript
;
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