UNPKG

@ledgerhq/hw-transport-node-hid-noevents

Version:

Ledger Hardware Wallet Node implementation of the communication layer, using node-hid. without usb events

168 lines 6.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDevices = getDevices; const node_hid_1 = __importDefault(require("node-hid")); const hw_transport_1 = __importDefault(require("@ledgerhq/hw-transport")); const devices_1 = require("@ledgerhq/devices"); const hid_framing_1 = __importDefault(require("./hid-framing")); const errors_1 = require("@ledgerhq/errors"); const filterInterface = device => ["win32", "darwin"].includes(process.platform) ? device.usagePage === 0xffa0 : device.interface === 0; function getDevices() { return node_hid_1.default.devices(devices_1.ledgerUSBVendorId, 0x0).filter(filterInterface); } /** * node-hid Transport minimal implementation * @example * import TransportNodeHid from "@ledgerhq/hw-transport-node-hid-noevents"; * ... * TransportNodeHid.create().then(transport => ...) */ class TransportNodeHidNoEvents extends hw_transport_1.default { /** * */ static isSupported = () => Promise.resolve(typeof node_hid_1.default.HID === "function"); /** * */ static list = () => Promise.resolve(getDevices().map(d => d.path)); /** */ static listen = (observer) => { getDevices().forEach(device => { const deviceModel = (0, devices_1.identifyUSBProductId)(device.productId); observer.next({ type: "add", descriptor: device.path, deviceModel, device: device, }); }); observer.complete(); return { unsubscribe: () => { }, }; }; /** * if path="" is not provided, the library will take the first device */ static open(path) { return Promise.resolve().then(() => { if (path) { return new TransportNodeHidNoEvents(new node_hid_1.default.HID(path)); } const device = getDevices()[0]; if (!device) throw new errors_1.TransportError("NoDevice", "NoDevice"); return new TransportNodeHidNoEvents(new node_hid_1.default.HID(device.path)); }); } device; deviceModel; channel = Math.floor(Math.random() * 0xffff); packetSize = 64; disconnected = false; constructor(device, { context, logType } = {}) { super({ context, logType }); this.updateTraceContext({ hidChannel: this.channel }); this.device = device; // @ts-expect-error accessing low level API in C const info = device.getDeviceInfo(); this.tracer.trace(`Connected to HID device ${!!info}`, { info }); this.deviceModel = info && info.product ? (0, devices_1.identifyProductName)(info.product) : null; } setDisconnected = () => { this.tracer.trace("Setting to disconnected", { alreadyDisconnected: this.disconnected }); if (!this.disconnected) { this.emit("disconnect"); this.disconnected = true; } }; writeHID = (content) => { const data = [0x00]; for (let i = 0; i < content.length; i++) { data.push(content[i]); } try { this.device.write(data); return Promise.resolve(); } catch (error) { this.tracer.trace(`Received an error during HID write: ${error}`, { error }); let maybeMappedError = error; if (error instanceof Error) { maybeMappedError = new errors_1.DisconnectedDeviceDuringOperation(error.message); } if (maybeMappedError instanceof errors_1.DisconnectedDeviceDuringOperation) { this.tracer.trace("Disconnected during HID write"); this.setDisconnected(); } return Promise.reject(maybeMappedError); } }; readHID = () => new Promise((resolve, reject) => this.device.read((e, res) => { if (!res) { return reject(new errors_1.DisconnectedDevice()); } if (e) { this.tracer.trace(`Received an error during HID read: ${e}`, { e }); const maybeMappedError = e && e.message ? new errors_1.DisconnectedDeviceDuringOperation(e.message) : e; if (maybeMappedError instanceof errors_1.DisconnectedDeviceDuringOperation) { this.tracer.trace("Disconnected during HID read"); this.setDisconnected(); } reject(maybeMappedError); } else { const buffer = Buffer.from(res); resolve(buffer); } })); /** * Exchange with the device using APDU protocol. * * @param apdu * @returns a promise of apdu response */ async exchange(apdu) { const tracer = this.tracer.withUpdatedContext({ function: "exchange", }); tracer.trace("Exchanging APDU ..."); const b = await this.exchangeAtomicImpl(async () => { const { channel, packetSize } = this; tracer.withType("apdu").trace(`=> ${apdu.toString("hex")}`, { channel, packetSize }); const framingHelper = (0, hid_framing_1.default)(channel, packetSize); // Creates HID-framed chunks from the APDU to be sent to the device... const blocks = framingHelper.makeBlocks(apdu); for (let i = 0; i < blocks.length; i++) { await this.writeHID(blocks[i]); } // Read... let result; let acc; while (!(result = framingHelper.getReducedResult(acc))) { const buffer = await this.readHID(); acc = framingHelper.reduceResponse(acc, buffer); } tracer.withType("apdu").trace(`<= ${result.toString("hex")}`, { channel, packetSize }); return result; }); return b; } setScrambleKey() { } /** * release the USB device. */ async close() { await this.exchangeBusyPromise; this.device.close(); } } exports.default = TransportNodeHidNoEvents; //# sourceMappingURL=TransportNodeHid.js.map