@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
277 lines • 13.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.onCallFirmwareUpdate = void 0;
const utils_1 = require("@trezor/utils");
const versionUtils_1 = require("@trezor/utils/lib/versionUtils");
const firmware_1 = require("../api/firmware");
const constants_1 = require("../constants");
const firmwareInfo_1 = require("../data/firmwareInfo");
const events_1 = require("../events");
const registerEvents = (device, postMessage) => {
device.on(events_1.DEVICE.BUTTON, (_d, request) => {
postMessage((0, events_1.createDeviceMessage)(events_1.DEVICE.BUTTON, {
code: request.code,
device: device.toMessageObject(),
}));
});
};
const waitForReconnectedDevice = async ({ bootloader, method, intermediary }, { deviceList, device, postMessage, log, abortSignal }) => {
const target = intermediary || !bootloader ? 'normal' : 'bootloader';
let i = 0;
if (method !== 'auto') {
log.debug('onCallFirmwareUpdate', 'waiting for device to disconnect');
postMessage((0, events_1.createUiMessage)(events_1.UI.FIRMWARE_RECONNECT, {
device: device.toMessageObject(),
disconnected: false,
method,
target,
i,
}));
await new Promise(resolve => {
deviceList.once('device-disconnect', resolve);
});
}
log.debug('onCallFirmwareUpdate', `waiting for device to reconnect in ${bootloader ? 'bootloader' : 'normal'} mode`);
let reconnectedDevice;
do {
postMessage((0, events_1.createUiMessage)(events_1.UI.FIRMWARE_RECONNECT, {
device: device.toMessageObject(),
disconnected: true,
method,
target,
i,
}));
await (0, utils_1.resolveAfter)(2000);
try {
reconnectedDevice = deviceList.getOnlyDevice();
}
catch {
}
if (reconnectedDevice &&
!reconnectedDevice.features &&
reconnectedDevice.handshakeFinished) {
log.debug('onCallFirmwareUpdate', 'we were unable to read device.features on the first interaction after seeing it, retrying...');
try {
await reconnectedDevice.run(undefined, {
skipFirmwareChecks: true,
skipLanguageChecks: true,
});
}
catch {
}
}
i++;
log.debug('onCallFirmwareUpdate', '...still waiting for device to reconnect', i);
} while (!abortSignal.aborted &&
(!reconnectedDevice?.features ||
bootloader === !reconnectedDevice.features.bootloader_mode ||
(intermediary &&
!(0, versionUtils_1.isNewer)([
reconnectedDevice.features.major_version,
reconnectedDevice.features.minor_version,
reconnectedDevice.features.patch_version,
], [
device.features.major_version,
device.features.minor_version,
device.features.patch_version,
]))));
if (!reconnectedDevice) {
throw constants_1.ERRORS.TypedError('Method_Interrupted');
}
registerEvents(reconnectedDevice, postMessage);
await reconnectedDevice.waitForFirstRun();
if (!reconnectedDevice.isUsedHere()) {
await reconnectedDevice.acquire();
}
return reconnectedDevice;
};
const getInstallationParams = (device, params) => {
const btcOnly = params.btcOnly ?? device.firmwareType === 'bitcoin-only';
if (!device.features.bootloader_mode) {
const version = params.binary
? (0, firmware_1.parseFirmwareHeaders)(Buffer.from(params.binary)).version
: undefined;
const isUpdatingToNewerVersion = !version
? device.firmwareRelease?.isNewer
: (0, versionUtils_1.isNewer)(version, [
device.features.major_version,
device.features.minor_version,
device.features.patch_version,
]);
const isUpdatingToEqualFirmwareType = (device.firmwareType === 'bitcoin-only') === btcOnly;
const upgrade = device.atLeast('2.6.3') && isUpdatingToNewerVersion && isUpdatingToEqualFirmwareType;
const manual = !device.atLeast(['1.10.0', '2.6.0']) && !upgrade;
const language = device.atLeast('2.7.0') &&
device.features.internal_model !== constants_1.PROTO.DeviceModelInternal.T2T1;
return {
manual,
upgrade,
language,
btcOnly,
};
}
else {
return {
manual: false,
upgrade: false,
language: false,
btcOnly,
};
}
};
const getFwHeader = (binary) => Buffer.from(binary.slice(0, 6000)).toString('hex');
const getBinaryHelper = (device, params, log, postMessage, btcOnly) => {
if (params.binary) {
return {
binary: params.binary,
binaryVersion: (0, firmware_1.parseFirmwareHeaders)(Buffer.from(params.binary)).version,
releaseVersion: undefined,
};
}
if (!device.firmwareRelease) {
throw constants_1.ERRORS.TypedError('Runtime', 'device.firmwareRelease is not set');
}
const { intermediaryVersion, release: { version }, } = device.firmwareRelease;
log.debug('onCallFirmwareUpdate loading binary', 'intermediaryVersion', intermediaryVersion, 'version', version, 'btcOnly', btcOnly);
postMessage((0, events_1.createUiMessage)(events_1.UI.FIRMWARE_PROGRESS, {
device: device.toMessageObject(),
operation: 'downloading',
progress: 0,
}));
return (0, firmware_1.getBinaryForFirmwareUpgrade)({
features: device.features,
releases: (0, firmwareInfo_1.getReleases)(device.features?.internal_model),
baseUrl: params.baseUrl || 'https://data.trezor.io',
version,
btcOnly,
intermediaryVersion,
})
.then(res => {
if (res.byteLength < 200) {
throw constants_1.ERRORS.TypedError('Runtime', 'Firmware binary is too small');
}
return res;
})
.then(res => {
postMessage((0, events_1.createUiMessage)(events_1.UI.FIRMWARE_PROGRESS, {
device: device.toMessageObject(),
operation: 'downloading',
progress: 100,
}));
return {
binary: res,
binaryVersion: (0, firmware_1.parseFirmwareHeaders)(Buffer.from(res)).version,
releaseVersion: version,
};
});
};
const onCallFirmwareUpdate = async ({ params, context: { deviceList, postMessage, initDevice, log, abortSignal }, }) => {
log.debug('onCallFirmwareUpdate with params: ', params);
const device = await initDevice(params?.device?.path);
if (deviceList.getDeviceCount() > 1) {
throw constants_1.ERRORS.TypedError('Device_MultipleNotSupported', 'Firmware update allowed with only 1 device connected');
}
log.debug('onCallFirmwareUpdate', 'device', device);
registerEvents(device, postMessage);
const { manual, upgrade, language, btcOnly } = getInstallationParams(device, params);
log.debug('onCallFirmwareUpdate', 'installation params', {
manual,
upgrade,
language,
btcOnly,
});
let binaryInfo = await getBinaryHelper(device, params, log, postMessage, btcOnly);
const { binary } = binaryInfo;
const deviceInitiallyConnectedInBootloader = device.features.bootloader_mode;
const deviceInitiallyConnectedWithoutFirmware = device.features.firmware_present === false;
let reconnectedDevice = device;
if (deviceInitiallyConnectedInBootloader) {
log.warn('onCallFirmwareUpdate', 'device is already in bootloader mode. language will not be updated');
await device.acquire();
}
else if (manual) {
reconnectedDevice = await waitForReconnectedDevice({ bootloader: true, method: 'manual' }, { deviceList, device, log, postMessage, abortSignal });
}
else {
const rebootParams = upgrade
? {
boot_command: constants_1.PROTO.BootCommand.INSTALL_UPGRADE,
firmware_header: getFwHeader(binary),
}
: {};
await device.acquire();
const targetLanguage = params.language || device.features.language || 'en-US';
const languageBlob = device.firmwareRelease && language && targetLanguage !== 'en-US'
? await (0, firmware_1.getLanguage)({
language: targetLanguage,
version: device.firmwareRelease.release.version,
internal_model: device.features.internal_model,
}).catch(() => {
})
: null;
const disconnectedPromise = new Promise(resolve => {
deviceList.once('device-disconnect', resolve);
});
if (!languageBlob) {
await device.getCommands().typedCall('RebootToBootloader', 'Success', rebootParams);
}
else {
let rebootResponse = await device.getCommands().typedCall('RebootToBootloader', ['TranslationDataRequest', 'Success'], { ...rebootParams, language_data_length: languageBlob?.byteLength });
log.debug('onCallFirmwareUpdate', 'RebootToBootloader response', rebootResponse.message);
while (languageBlob && rebootResponse.type !== 'Success') {
const start = rebootResponse.message.data_offset;
const end = rebootResponse.message.data_offset + rebootResponse.message.data_length;
const chunk = languageBlob.slice(start, end);
rebootResponse = await device.getCommands().typedCall('TranslationDataAck', ['TranslationDataRequest', 'Success'], { data_chunk: Buffer.from(chunk).toString('hex') });
}
}
log.info('onCallFirmwareUpdate', 'waiting for disconnected event after rebootToBootloader...');
await disconnectedPromise;
if (device.features.major_version === 1) {
await (0, utils_1.resolveAfter)(2000);
}
reconnectedDevice = await waitForReconnectedDevice({ bootloader: true, method: 'auto' }, { deviceList, device, log, postMessage, abortSignal });
}
const intermediary = !params.binary && device.firmwareRelease?.intermediaryVersion;
const bootloaderVersion = reconnectedDevice.getVersion();
await reconnectedDevice.initialize(false);
let stripped = (0, firmware_1.stripFwHeaders)(binary);
await (0, firmware_1.uploadFirmware)(reconnectedDevice.getCommands().typedCall.bind(reconnectedDevice.getCommands()), postMessage, reconnectedDevice, { payload: !intermediary && (0, firmware_1.shouldStripFwHeaders)(device.features) ? stripped : binary });
log.info('onCallFirmwareUpdate', 'firmware uploaded');
if (intermediary) {
log.info('onCallFirmwareUpdate', '...but it was the intermediary firmware, so one more go');
reconnectedDevice = await waitForReconnectedDevice({ bootloader: true, method: 'manual', intermediary: true }, { deviceList, device: reconnectedDevice, log, postMessage, abortSignal });
binaryInfo = await getBinaryHelper(reconnectedDevice, params, log, postMessage, btcOnly);
stripped = (0, firmware_1.stripFwHeaders)(binaryInfo.binary);
await reconnectedDevice.initialize(false);
await (0, firmware_1.uploadFirmware)(reconnectedDevice.getCommands().typedCall.bind(reconnectedDevice.getCommands()), postMessage, reconnectedDevice, { payload: stripped });
}
reconnectedDevice = await waitForReconnectedDevice({ bootloader: false, method: 'wait' }, { deviceList, device: reconnectedDevice, log, postMessage, abortSignal });
if (reconnectedDevice.atLeast('2.7.0') &&
deviceInitiallyConnectedWithoutFirmware &&
params.language) {
try {
log.info('onCallFirmwareUpdate', 'changing language for fresh device to: ', params.language);
await reconnectedDevice.changeLanguage({ language: params.language });
}
catch (err) {
log.error('onCallFirmwareUpdate', 'changeLanguage failed silently: ', err);
}
}
const installedVersion = reconnectedDevice.getVersion();
if (!bootloaderVersion || !installedVersion) {
throw constants_1.ERRORS.TypedError('Runtime', 'reconnectedDevice.installedVersion is not set');
}
const { binaryVersion, releaseVersion } = binaryInfo;
const assertBinaryVersion = (0, versionUtils_1.isEqual)(installedVersion, binaryVersion);
const assertReleaseVersion = releaseVersion ? (0, versionUtils_1.isEqual)(installedVersion, releaseVersion) : true;
return {
versionCheck: assertBinaryVersion && assertReleaseVersion,
bootloaderVersion,
installedVersion,
binaryVersion,
releaseVersion,
};
};
exports.onCallFirmwareUpdate = onCallFirmwareUpdate;
//# sourceMappingURL=onCallFirmwareUpdate.js.map