UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

844 lines 32.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Device = exports.GET_FEATURES_TIMEOUT_REACT_NATIVE = exports.GET_FEATURES_TIMEOUT = exports.CANCEL_TIMEOUT = void 0; const crypto_1 = require("crypto"); const protocol_1 = require("@trezor/protocol"); const transport_1 = require("@trezor/transport"); const utils_1 = require("@trezor/utils"); const DeviceCommands_1 = require("./DeviceCommands"); const constants_1 = require("../constants"); const checkFirmwareRevision_1 = require("./checkFirmwareRevision"); const prompts_1 = require("./prompts"); const firmware_1 = require("../api/firmware"); const DataManager_1 = require("../data/DataManager"); const coinInfo_1 = require("../data/coinInfo"); const firmwareInfo_1 = require("../data/firmwareInfo"); const getLanguage_1 = require("../data/getLanguage"); const models_1 = require("../data/models"); const events_1 = require("../events"); const types_1 = require("../types"); const debug_1 = require("../utils/debug"); const deviceFeaturesUtils_1 = require("../utils/deviceFeaturesUtils"); const _log = (0, debug_1.initLog)('Device'); exports.CANCEL_TIMEOUT = 1_000; exports.GET_FEATURES_TIMEOUT = 3_000; exports.GET_FEATURES_TIMEOUT_REACT_NATIVE = 20_000; const parseRunOptions = (options) => { if (!options) options = {}; return options; }; class Device extends utils_1.TypedEmitter { transport; protocol; transportPath; transportSessionOwner; transportDescriptorType; bluetoothProps; session; lastAcquiredHere; unreadableError; _firmwareStatus; get firmwareStatus() { return this._firmwareStatus; } _firmwareRelease; get firmwareRelease() { return this._firmwareRelease; } _features; get features() { return this._features; } _featuresNeedsReload = false; acquirePromise; releasePromise; runPromise; keepTransportSession = false; commands; cancelableAction; loaded = false; inconsistent = false; firstRunPromise; instance = 0; state = []; stateStorage = undefined; _unavailableCapabilities = {}; get unavailableCapabilities() { return this._unavailableCapabilities; } _firmwareType; get firmwareType() { return this._firmwareType; } name = 'Trezor'; color; availableTranslations = []; authenticityChecks = { firmwareRevision: null, firmwareHash: null, }; uniquePath; emitLifecycle; sessionDfd; handshakeFinished = false; constructor({ id, transport, descriptor, listener }) { super(); this.emitLifecycle = listener; this.protocol = protocol_1.v1; this.uniquePath = id; this.transport = transport; this.transportPath = descriptor.path; this.transportSessionOwner = descriptor.sessionOwner; this.transportDescriptorType = descriptor.type; this.bluetoothProps = descriptor.id ? { id: descriptor.id } : undefined; this.session = descriptor.session; this.lastAcquiredHere = false; this.firstRunPromise = (0, utils_1.createDeferred)(); } getSessionChangePromise() { if (!this.sessionDfd) { this.sessionDfd = (0, utils_1.createDeferred)(); this.sessionDfd.promise .catch(() => { }) .finally(() => { this.sessionDfd = undefined; }); } return this.sessionDfd.promise; } async waitAndCompareSession(response, sessionPromise) { if (response.success) { try { if ((await sessionPromise) !== response.payload) { return { success: false, error: transport_1.TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS, }; } } catch { return { success: false, error: transport_1.TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION, }; } } return response; } acquire() { const sessionPromise = this.getSessionChangePromise(); this.acquirePromise = this.transport .acquire({ input: { path: this.transportPath, previous: this.session } }) .then(result => this.waitAndCompareSession(result, sessionPromise)) .then(result => { if (result.success) { this.session = result.payload; this.lastAcquiredHere = true; this.commands?.dispose(); this.commands = new DeviceCommands_1.DeviceCommands(this, this.transport, this.session); return result; } else { if (this.runPromise) { this.runPromise.reject(new Error(result.error)); delete this.runPromise; } throw result.error; } }) .finally(() => { this.acquirePromise = undefined; }); return this.acquirePromise; } async release() { const localSession = this.getLocalSession(); if (!localSession || this.keepTransportSession || this.releasePromise) { return; } if (this.commands) { this.commands.dispose(); if (this.commands.callPromise) { await this.commands.callPromise; } } const sessionPromise = this.getSessionChangePromise(); this.releasePromise = this.transport .release({ session: localSession, path: this.transportPath }) .then(result => this.waitAndCompareSession(result, sessionPromise)) .then(result => { if (result.success) { this.session = null; } return result; }) .finally(() => { this.releasePromise = undefined; }); return this.releasePromise; } releaseTransportSession() { this.keepTransportSession = false; } async cleanup(release = true) { this.eventNames().forEach(e => this.removeAllListeners(e)); delete this.runPromise; if (release) { await this.release(); } } async handshake(delay) { if (delay) { await (0, utils_1.resolveAfter)(501 + delay); } while (true) { if (this.isUsedElsewhere()) { this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED); } else { try { await this.run(); } catch (error) { _log.warn(`device.run error.message: ${error.message}, code: ${error.code}`); if (error.code === 'Device_NotFound' || error.message === transport_1.TRANSPORT_ERROR.DEVICE_NOT_FOUND || error.message === transport_1.TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION || error.message === transport_1.TRANSPORT_ERROR.DESCRIPTOR_NOT_FOUND || error.message === transport_1.TRANSPORT_ERROR.HTTP_ERROR) { } else if (error.message === transport_1.TRANSPORT_ERROR.UNEXPECTED_ERROR || error.message === transport_1.TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS || error.code === 'Device_UsedElsewhere' || error.code === 'Device_InitializeFailed') { this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED); } else if (error.message === transport_1.TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE || error.message?.indexOf(constants_1.ERRORS.LIBUSB_ERROR_MESSAGE) >= 0) { this.unreadableError = error?.message; this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED); } else { await (0, utils_1.resolveAfter)(501); continue; } } } this.handshakeFinished = true; return; } } async updateDescriptor(descriptor) { this.sessionDfd?.resolve(descriptor.session); await Promise.all([this.acquirePromise, this.releasePromise]); if (descriptor.session && descriptor.session !== this.session) { this.usedElsewhere(); } if (!descriptor.session) { const methodStillRunning = !this.commands?.isDisposed(); if (methodStillRunning) { this.releaseTransportSession(); } } this.session = descriptor.session; this.emitLifecycle(events_1.DEVICE.CHANGED); } run(fn, options) { if (this.runPromise) { _log.warn('Previous call is still running'); throw constants_1.ERRORS.TypedError('Device_CallInProgress'); } options = parseRunOptions(options); const wasUnacquired = this.isUnacquired(); const runPromise = (0, utils_1.createDeferred)(); this.runPromise = runPromise; this._runInner(fn, options) .then(() => { if (wasUnacquired && !this.isUnacquired()) { this.emitLifecycle(events_1.DEVICE.CONNECT); } }) .catch(err => { runPromise.reject(err); }); return runPromise.promise; } async override(error) { if (this.acquirePromise) { await this.acquirePromise; } if (this.runPromise) { await this.interruptionFromUser(error); } if (this.releasePromise) { await this.releasePromise; } } setCancelableAction(callback) { this.cancelableAction = (e) => callback(e) .catch(e2 => { _log.debug('cancelableAction error', e2); }) .finally(() => { this.clearCancelableAction(); }); } clearCancelableAction() { this.cancelableAction = undefined; } async interruptionFromUser(error) { _log.debug('interruptionFromUser'); await this.cancelableAction?.(error); await this.commands?.cancel(); if (this.runPromise) { this.runPromise.reject(error); delete this.runPromise; } } usedElsewhere() { if (!this.lastAcquiredHere) { return; } this.lastAcquiredHere = false; this._featuresNeedsReload = true; _log.debug('interruptionFromOutside'); if (this.commands) { this.commands.dispose(); } if (this.runPromise) { this.runPromise.reject(constants_1.ERRORS.TypedError('Device_UsedElsewhere')); delete this.runPromise; } if (this.session) { this.transport.releaseDevice(this.session); } } async _runInner(fn, options) { if (this.releasePromise) { await this.releasePromise; } const acquireNeeded = !this.isUsedHere() || this.commands?.disposed; if (acquireNeeded) { await this.acquire(); } const { staticSessionId, deriveCardano } = this.getState() || {}; if (acquireNeeded || !staticSessionId || (!deriveCardano && options.useCardanoDerivation)) { try { if (fn) { await this.initialize(!!options.useCardanoDerivation); } else { const isNative = DataManager_1.DataManager.getSettings('env') === 'react-native'; const getFeaturesTimeout = isNative ? exports.GET_FEATURES_TIMEOUT_REACT_NATIVE : exports.GET_FEATURES_TIMEOUT; const cancelTimeout = isNative ? exports.GET_FEATURES_TIMEOUT_REACT_NATIVE : exports.CANCEL_TIMEOUT; if (['v1', 'bridge'].includes(this.protocol.name) && ![0, 2].includes(this.transportDescriptorType)) { _log.debug('sending a preventive cancel on the first encounter with the device'); await Promise.race([ this.getCommands() .typedCall('Cancel', 'Failure', {}) .catch(() => { }), new Promise((_, reject) => setTimeout(reject, cancelTimeout)), ]).catch(() => this.acquire()); } let getFeaturesTimeoutId; await Promise.race([ this.getFeatures().finally(() => clearTimeout(getFeaturesTimeoutId)), new Promise((_resolve, reject) => { getFeaturesTimeoutId = setTimeout(() => { (0, prompts_1.cancelPrompt)(this, false).finally(() => { reject(new Error('GetFeatures timeout')); }); }, getFeaturesTimeout); }), ]); } } catch (error) { _log.warn('Device._runInner error: ', error.message); if (!this.inconsistent && (error.message === 'GetFeatures timeout' || error.message === 'Unknown message')) { this.inconsistent = true; return this._runInner(() => Promise.resolve({}), options); } if (transport_1.TRANSPORT_ERROR.ABORTED_BY_TIMEOUT === error.message) { this.unreadableError = 'Connection timeout'; } this.inconsistent = true; delete this.runPromise; return Promise.reject(constants_1.ERRORS.TypedError('Device_InitializeFailed', `Initialize failed: ${error.message}${error.code ? `, code: ${error.code}` : ''}`)); } } if (!options.skipFirmwareChecks) { await this.checkFirmwareHashWithRetries(); await this.checkFirmwareRevisionWithRetries(); } if (!options.skipLanguageChecks && this.features?.language && !this.features.language_version_matches && this.atLeast('2.7.0')) { _log.info('language version mismatch. silently updating...'); try { await this.changeLanguage({ language: this.features.language }); } catch (err) { _log.error('change language failed silently', err); } } if (options.keepSession) { this.keepTransportSession = true; } if (fn) { await fn(); } if (this.loaded && this.features && !options.skipFinalReload) { await this.getFeatures(); } if ((!this.keepTransportSession && typeof options.keepSession !== 'boolean') || options.keepSession === false) { this.keepTransportSession = false; await this.release(); } if (this.runPromise) { this.runPromise.resolve(); } delete this.runPromise; if (!this.loaded) { this.loaded = true; this.firstRunPromise.resolve(true); } } getCommands() { if (!this.commands) { throw constants_1.ERRORS.TypedError('Runtime', `Device: commands not defined`); } return this.commands; } setInstance(instance = 0) { if (this.instance !== instance) { if (this.keepTransportSession) { this.lastAcquiredHere = false; this.keepTransportSession = false; } } this.instance = instance; } getInstance() { return this.instance; } getState() { return this.state[this.instance]; } setState(state) { if (!state) { delete this.state[this.instance]; } else { const prevState = this.state[this.instance]; const newState = { ...prevState, ...state, }; this.state[this.instance] = newState; this.stateStorage?.saveState(this, newState); } } async validateState(preauthorized = false) { if (!this.features) return; if (!this.features.unlocked && preauthorized) { if (await this.getCommands().preauthorize(false)) { return; } } const expectedState = this.getState()?.staticSessionId; const state = await this.getCommands().getDeviceState(); const uniqueState = `${state}@${this.features.device_id}:${this.instance}`; if (this.features.session_id) { this.setState({ sessionId: this.features.session_id }); } if (expectedState && expectedState !== uniqueState) { return uniqueState; } if (!expectedState) { this.setState({ staticSessionId: uniqueState }); } } async initialize(useCardanoDerivation) { let payload; if (this.features) { const { sessionId, deriveCardano } = this.getState() || {}; payload = { derive_cardano: deriveCardano || useCardanoDerivation, }; if (sessionId) { payload.session_id = sessionId; } } const { message } = await this.getCommands().typedCall('Initialize', 'Features', payload); this._updateFeatures(message); this.setState({ deriveCardano: payload?.derive_cardano }); } initStorage(storage) { this.stateStorage = storage; this.setState(storage.loadState(this)); } async getFeatures() { const { message } = await this.getCommands().typedCall('GetFeatures', 'Features', {}); this._updateFeatures(message); } async checkFirmwareHashWithRetries() { const lastResult = this.authenticityChecks.firmwareHash; const notDoneYet = lastResult === null; const attemptsDone = lastResult?.attemptCount ?? 0; if (attemptsDone >= constants_1.FIRMWARE.HASH_CHECK_MAX_ATTEMPTS) return; const wasError = lastResult !== null && !lastResult.success; const wasErrorRetriable = wasError && (0, utils_1.isArrayMember)(lastResult.error, constants_1.FIRMWARE.HASH_CHECK_RETRIABLE_ERRORS); const lastErrorPayload = wasError ? lastResult?.errorPayload : null; if (notDoneYet || wasErrorRetriable) { const result = await this.checkFirmwareHash(); this.authenticityChecks.firmwareHash = result; if (result === null) return; result.attemptCount = attemptsDone + 1; if (result.success && lastErrorPayload) { result.warningPayload = { lastErrorPayload, successOnAttempt: result.attemptCount }; } } } async checkFirmwareHash() { const createFailResult = (error, errorPayload) => ({ success: false, error, errorPayload, }); const baseUrl = DataManager_1.DataManager.getSettings('binFilesBaseUrl'); const enabled = DataManager_1.DataManager.getSettings('enableFirmwareHashCheck'); if (!enabled || baseUrl === undefined) return createFailResult('check-skipped'); const firmwareVersion = this.getVersion(); if (firmwareVersion === undefined || !this.features || this.features.bootloader_mode) { return null; } const checkSupported = this.atLeast(constants_1.FIRMWARE.FW_HASH_SUPPORTED_VERSIONS); if (!checkSupported) return createFailResult('check-unsupported'); const release = (0, firmwareInfo_1.getReleases)(this.features.internal_model).find(r => utils_1.versionUtils.isEqual(r.version, firmwareVersion)); if (release === undefined) return createFailResult('unknown-release'); const btcOnly = this.firmwareType === types_1.FirmwareType.BitcoinOnly; const binary = await (0, firmware_1.getBinaryOptional)({ baseUrl, btcOnly, release }); if (binary === null) { return createFailResult('check-unsupported'); } if (binary.byteLength < 200) { _log.warn(`Firmware binary for hash check suspiciously small (< 200 b)`); return createFailResult('check-unsupported'); } const strippedBinary = (0, firmware_1.stripFwHeaders)(binary); const { hash: expectedHash, challenge } = (0, firmware_1.calculateFirmwareHash)(this.features.major_version, strippedBinary, (0, crypto_1.randomBytes)(32)); try { const deviceResponse = await this.getCommands().typedCall('GetFirmwareHash', 'FirmwareHash', { challenge }); if (!deviceResponse?.message?.hash) { return createFailResult('other-error', 'Device response is missing hash'); } if (deviceResponse.message.hash !== expectedHash) { return createFailResult('hash-mismatch'); } return { success: true }; } catch (errorPayload) { return createFailResult('other-error', (0, utils_1.serializeError)(errorPayload)); } } async checkFirmwareRevisionWithRetries() { const lastResult = this.authenticityChecks.firmwareRevision; const notDoneYet = lastResult === null; const wasError = lastResult !== null && !lastResult.success; const wasErrorRetriable = wasError && (0, utils_1.isArrayMember)(lastResult.error, constants_1.FIRMWARE.REVISION_CHECK_RETRIABLE_ERRORS); if (notDoneYet || wasErrorRetriable) { await this.checkFirmwareRevision(); } } async checkFirmwareRevision() { const firmwareVersion = this.getVersion(); if (!firmwareVersion || !this.features) { return; } if (this.features.bootloader_mode === true) { return; } const releases = (0, firmwareInfo_1.getReleases)(this.features.internal_model); const release = releases.find(r => firmwareVersion && utils_1.versionUtils.isVersionArray(firmwareVersion) && utils_1.versionUtils.isEqual(r.version, firmwareVersion)); this.authenticityChecks.firmwareRevision = await (0, checkFirmwareRevision_1.checkFirmwareRevision)({ internalModel: this.features.internal_model, deviceRevision: this.features.revision, firmwareVersion, expectedRevision: release?.firmware_revision, }); } async changeLanguage({ language, binary, }) { if (language === 'en-US') { return this._uploadTranslationData(null); } if (binary) { return this._uploadTranslationData(binary); } const version = this.getVersion(); if (!version) { throw constants_1.ERRORS.TypedError('Runtime', 'changeLanguage: device version unknown'); } const downloadedBinary = await (0, getLanguage_1.getLanguage)({ language, version, internal_model: this.features.internal_model, }); if (!downloadedBinary) { throw constants_1.ERRORS.TypedError('Runtime', 'changeLanguage: translation not found'); } return this._uploadTranslationData(downloadedBinary); } async _uploadTranslationData(payload) { if (!this.commands) { throw constants_1.ERRORS.TypedError('Runtime', 'uploadTranslationData: device.commands is not set'); } if (payload === null) { const response = await this.commands.typedCall('ChangeLanguage', ['Success'], { data_length: 0 }); return response.message; } const length = payload.byteLength; let response = await this.commands.typedCall('ChangeLanguage', ['TranslationDataRequest', 'Success'], { data_length: length }); while (response.type !== 'Success') { const start = response.message.data_offset; const end = response.message.data_offset + response.message.data_length; const chunk = payload.slice(start, end); response = await this.commands.typedCall('TranslationDataAck', ['TranslationDataRequest', 'Success'], { data_chunk: Buffer.from(chunk).toString('hex'), }); } return response.message; } _updateFeatures(feat) { const capabilities = (0, deviceFeaturesUtils_1.parseCapabilities)(feat); feat.capabilities = capabilities; if (this.features && this.features.session_id && !feat.session_id) { feat.session_id = this.features.session_id; } feat.unlocked = feat.unlocked ?? true; const revision = (0, deviceFeaturesUtils_1.parseRevision)(feat); feat.revision = revision; if (!feat.model && feat.major_version === 1) { feat.model = '1'; } if (!feat.internal_model || !types_1.DeviceModelInternal[feat.internal_model]) { feat.internal_model = (0, deviceFeaturesUtils_1.ensureInternalModelFeature)(feat.model); } const version = this.getVersion(); const newVersion = [ feat.major_version, feat.minor_version, feat.patch_version, ]; if (!version || !utils_1.versionUtils.isEqual(version, newVersion)) { if (version) { this.emit(events_1.DEVICE.FIRMWARE_VERSION_CHANGED, { oldVersion: version, newVersion, device: this.toMessageObject(), }); } this._unavailableCapabilities = (0, deviceFeaturesUtils_1.getUnavailableCapabilities)(feat, (0, coinInfo_1.getAllNetworks)()); this._firmwareStatus = (0, firmwareInfo_1.getFirmwareStatus)(feat); this._firmwareRelease = (0, firmwareInfo_1.getRelease)(feat); this.availableTranslations = this.firmwareRelease?.translations ?? []; } this._features = feat; this._featuresNeedsReload = false; if (feat.fw_vendor === 'Trezor Bitcoin-only') { this._firmwareType = types_1.FirmwareType.BitcoinOnly; } else if (feat.fw_vendor === 'Trezor') { this._firmwareType = types_1.FirmwareType.Regular; } else if (this.getMode() !== 'bootloader') { this._firmwareType = feat.capabilities && feat.capabilities.length > 0 && !feat.capabilities.includes('Capability_Bitcoin_like') ? types_1.FirmwareType.BitcoinOnly : types_1.FirmwareType.Regular; } const deviceInfo = models_1.models[feat.internal_model] ?? { name: `Unknown ${feat.internal_model}`, colors: {}, }; this.name = deviceInfo.name; if (feat?.unit_color) { const deviceUnitColor = feat.unit_color.toString(); if (deviceUnitColor in deviceInfo.colors) { this.color = deviceInfo.colors[deviceUnitColor]; } } } isUnacquired() { return this.features === undefined; } isUnreadable() { return !!this.unreadableError; } disconnect() { _log.debug('Disconnect cleanup'); this.sessionDfd?.reject(new Error()); this.lastAcquiredHere = false; this.emitLifecycle(events_1.DEVICE.DISCONNECT); return this.interruptionFromUser(constants_1.ERRORS.TypedError('Device_Disconnected')); } isBootloader() { return this.features && !!this.features.bootloader_mode; } isInitialized() { return this.features && !!this.features.initialized; } isSeedless() { return this.features && !!this.features.no_backup; } isInconsistent() { return this.inconsistent; } getVersion() { if (!this.features) return; return [ this.features.major_version, this.features.minor_version, this.features.patch_version, ]; } atLeast(versions) { const version = this.getVersion(); if (!this.features || !version) return false; const modelVersion = typeof versions === 'string' ? versions : versions[this.features.major_version - 1]; return utils_1.versionUtils.isNewerOrEqual(version, modelVersion); } isUsed() { return typeof this.session === 'string'; } isUsedHere() { return this.isUsed() && this.lastAcquiredHere; } isUsedElsewhere() { return this.isUsed() && !this.lastAcquiredHere; } isRunning() { return !!this.runPromise; } isLoaded() { return this.loaded; } waitForFirstRun() { return this.firstRunPromise.promise; } getLocalSession() { return this.lastAcquiredHere ? this.session : null; } getUniquePath() { return this.uniquePath; } isT1() { return this.features ? this.features.major_version === 1 : false; } hasUnexpectedMode(allow, require) { if (this.features) { if (this.isBootloader() && !allow.includes(events_1.UI.BOOTLOADER)) { return events_1.UI.BOOTLOADER; } if (!this.isInitialized() && !allow.includes(events_1.UI.INITIALIZE)) { return events_1.UI.INITIALIZE; } if (this.isSeedless() && !allow.includes(events_1.UI.SEEDLESS)) { return events_1.UI.SEEDLESS; } if (!this.isBootloader() && require.includes(events_1.UI.BOOTLOADER)) { return events_1.UI.NOT_IN_BOOTLOADER; } } return null; } dispose() { this.removeAllListeners(); if (this.session && this.lastAcquiredHere) { return this.transport.release({ session: this.session, path: this.transportPath, onClose: true, }); } } getMode() { if (this.features.bootloader_mode) return 'bootloader'; if (!this.features.initialized) return 'initialize'; if (this.features.no_backup) return 'seedless'; return 'normal'; } toMessageObject() { const { name, uniquePath: path } = this; const base = { path, name }; if (this.unreadableError) { return { ...base, type: 'unreadable', error: this.unreadableError, label: 'Unreadable device', transportDescriptorType: this.transportDescriptorType, }; } if (this.isUnacquired()) { return { ...base, type: 'unacquired', label: 'Unacquired device', name: this.name, transportSessionOwner: this.transportSessionOwner, bluetoothProps: this.bluetoothProps, }; } const defaultLabel = 'My Trezor'; const label = this.features.label === '' || !this.features.label ? defaultLabel : this.features.label; let status = this.isUsedElsewhere() ? 'occupied' : 'available'; if (this._featuresNeedsReload) status = 'used'; return { ...base, type: 'acquired', id: this.features.device_id, label, _state: this.getState(), state: this.getState()?.staticSessionId, status, mode: this.getMode(), color: this.color, firmware: this.firmwareStatus, firmwareRelease: this.firmwareRelease, firmwareType: this.firmwareType, features: this.features, unavailableCapabilities: this.unavailableCapabilities, availableTranslations: this.availableTranslations, authenticityChecks: this.authenticityChecks, bluetoothProps: this.bluetoothProps, }; } } exports.Device = Device; //# sourceMappingURL=Device.js.map