@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
817 lines • 36.4 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 DeviceList_1 = require("../device/DeviceList");
const StateStorage_1 = require("../device/StateStorage");
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 MAX_PIN_TRIES = 3;
const getInvalidDeviceState = async ({ sendCoreMessage }, device, preauthorized) => {
for (let i = 0; i < MAX_PIN_TRIES - 1; ++i) {
try {
return await device.validateState(preauthorized);
}
catch (error) {
if (error.message.includes('PIN invalid')) {
sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.INVALID_PIN, { device: device.toMessageObject() }));
}
else {
throw error;
}
}
}
return device.validateState(preauthorized);
};
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) {
device.releaseTransportSession();
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 isDeviceUnlocked = device.features.unlocked;
if (method.useDeviceState) {
try {
let invalidDeviceState = await getInvalidDeviceState(context, device, method.preauthorized);
if (isUsingPopup) {
while (invalidDeviceState) {
const uiPromise = uiPromises.create(events_2.UI.INVALID_PASSPHRASE_ACTION, device);
sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.INVALID_PASSPHRASE, {
device: device.toMessageObject(),
}));
const uiResp = await uiPromise.promise;
if (uiResp.payload) {
device.setState({ sessionId: undefined });
await device.initialize(method.useCardanoDerivation);
invalidDeviceState = await getInvalidDeviceState(context, device, method.preauthorized);
}
else {
device.setState({ staticSessionId: invalidDeviceState });
break;
}
}
}
else if (invalidDeviceState) {
throw constants_1.ERRORS.TypedError('Device_InvalidState');
}
}
catch (error) {
device.setState({ sessionId: undefined });
return Promise.reject(error);
}
}
if (!isDeviceUnlocked && device.features.unlocked) {
sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.CHANGED, device.toMessageObject()));
}
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();
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, getOverridePromise, setOverridePromise, 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');
setOverridePromise(device.override(overrideError));
await getOverridePromise();
if (method.overridden) {
sendCoreMessage((0, events_2.createResponseMessage)(method.responseID, false, { error: overrideError }));
throw overrideError;
}
}
else if (device.isRunning()) {
if (!device.isLoaded()) {
await device.waitForFirstRun();
}
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);
}
device.on(events_2.DEVICE.BUTTON, onDeviceButtonHandler(context, method));
device.on(events_2.DEVICE.PIN, onDevicePinHandler(context));
device.on(events_2.DEVICE.WORD, onDeviceWordHandler(context));
device.on(events_2.DEVICE.PASSPHRASE, (method.useEmptyPassphrase ? onEmptyPassphraseHandler : onDevicePassphraseHandler)(context));
device.on(events_2.DEVICE.PASSPHRASE_ON_DEVICE, () => {
sendCoreMessage((0, events_2.createUiMessage)(events_2.UI.REQUEST_PASSPHRASE_ON_DEVICE, { device: device.toMessageObject() }));
});
device.on(events_2.DEVICE.FIRMWARE_VERSION_CHANGED, payload => {
sendCoreMessage((0, events_2.createDeviceMessage)(events_2.DEVICE.FIRMWARE_VERSION_CHANGED, payload));
});
if (useCoreInPopup && env === 'webextension' && origin) {
device.initStorage(new StateStorage_1.WebextensionStateStorage(origin));
}
let messageResponse;
try {
if (getOverridePromise()) {
await getOverridePromise();
}
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.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 (getOverridePromise()) {
await getOverridePromise();
}
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) {
const shouldReleaseSession = response.success ||
(!response.success &&
response?.payload?.error !== 'device disconnected during action');
await device.cleanup(shouldReleaseSession);
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 = (context, method) => async (...[device, 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)
: 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 = (context) => async (...[device, 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;
callback(uiResp.payload);
}
catch (error) {
callback(null, error);
}
};
const onDeviceWordHandler = (context) => async (...[device, 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;
callback(uiResp.payload);
}
catch (error) {
callback(null, error);
}
};
const onDevicePassphraseHandler = (context) => async (...[device, 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;
callback(uiResp.payload);
}
catch (error) {
callback(null, error);
}
};
const onEmptyPassphraseHandler = () => (...[_, callback]) => {
callback({ value: '' });
};
const onPopupClosed = (context, customErrorMessage) => {
const { uiPromises, popupPromise, deviceList, callMethods, resetWaitForFirstMethod, setOverridePromise, 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 => {
d.releaseTransportSession();
if (d.isUsedHere()) {
setOverridePromise(d.interruptionFromUser(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()));
overridePromise;
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 {
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();
},
getOverridePromise: () => this.overridePromise,
setOverridePromise: (promise) => {
this.overridePromise = promise;
},
};
}
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_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.IFRAME.CALL:
if (message.payload.method === 'firmwareUpdate') {
(0, DeviceList_1.assertDeviceListConnected)(this.deviceList);
(0, onCallFirmwareUpdate_1.onCallFirmwareUpdate)({
params: message.payload,
context: {
deviceList: this.deviceList,
postMessage: this.sendCoreMessage.bind(this),
initDevice: path => initDevice(this.getCoreContext(), { path }),
log: _log,
abortSignal: this.abortController.signal,
},
})
.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 { debug, priority, _sessionsBackgroundUrl, 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,
_sessionsBackgroundUrl,
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