@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
1,006 lines (1,005 loc) • 34.6 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);
}
if (!device && 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 = method.getMethodInfo();
if (method.payload.__precomposed) {
response.precomposed = await method.payloadToPrecomposed();
}
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(events_2.DEVICE.TREZOR_PUSH_NOTIFICATION, payload => {
sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.TREZOR_PUSH_NOTIFICATION, {
device: payload.device.toMessageObject(),
mode: payload.mode,
type: payload.type
}));
});
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),
uiPromises: coreContext.uiPromises
}
}).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