UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

277 lines 13.1 kB
"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