UNPKG

@ledgerhq/hw-transport-webhid

Version:
231 lines 8.68 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import Transport from "@ledgerhq/hw-transport"; import hidFraming from "@ledgerhq/devices/hid-framing"; import { identifyUSBProductId, ledgerUSBVendorId } from "@ledgerhq/devices"; import { log } from "@ledgerhq/logs"; import { TransportOpenUserCancelled, DisconnectedDeviceDuringOperation, DisconnectedDevice, TransportError, } from "@ledgerhq/errors"; const ledgerDevices = [ { vendorId: ledgerUSBVendorId, }, ]; const isSupported = () => Promise.resolve(!!(window.navigator && window.navigator.hid)); const getHID = () => { // $FlowFixMe const { hid } = navigator; if (!hid) throw new TransportError("navigator.hid is not supported", "HIDNotSupported"); return hid; }; function requestLedgerDevices() { return __awaiter(this, void 0, void 0, function* () { const device = yield getHID().requestDevice({ filters: ledgerDevices, }); if (Array.isArray(device)) return device; return [device]; }); } function getLedgerDevices() { return __awaiter(this, void 0, void 0, function* () { const devices = yield getHID().getDevices(); return devices.filter(d => d.vendorId === ledgerUSBVendorId); }); } function getFirstLedgerDevice() { return __awaiter(this, void 0, void 0, function* () { const existingDevices = yield getLedgerDevices(); if (existingDevices.length > 0) return existingDevices[0]; const devices = yield requestLedgerDevices(); return devices[0]; }); } /** * WebHID Transport implementation * @example * import TransportWebHID from "@ledgerhq/hw-transport-webhid"; * ... * TransportWebHID.create().then(transport => ...) */ class TransportWebHID extends Transport { constructor(device) { super(); this.channel = Math.floor(Math.random() * 0xffff); this.packetSize = 64; this.inputs = []; this.read = () => { if (this.inputs.length) { return Promise.resolve(this.inputs.shift()); } return new Promise(success => { this.inputCallback = success; }); }; this.onInputReport = (e) => { const buffer = Buffer.from(e.data.buffer); if (this.inputCallback) { this.inputCallback(buffer); this.inputCallback = null; } else { this.inputs.push(buffer); } }; this._disconnectEmitted = false; this._emitDisconnect = (e) => { if (this._disconnectEmitted) return; this._disconnectEmitted = true; this.emit("disconnect", e); }; /** * Exchange with the device using APDU protocol. * @param apdu * @returns a promise of apdu response */ this.exchange = (apdu) => __awaiter(this, void 0, void 0, function* () { const b = yield this.exchangeAtomicImpl(() => __awaiter(this, void 0, void 0, function* () { const { channel, packetSize } = this; log("apdu", "=> " + apdu.toString("hex")); const framing = hidFraming(channel, packetSize); // Write... const blocks = framing.makeBlocks(apdu); for (let i = 0; i < blocks.length; i++) { yield this.device.sendReport(0, blocks[i]); } // Read... let result; let acc; while (!(result = framing.getReducedResult(acc))) { try { const buffer = yield this.read(); acc = framing.reduceResponse(acc, buffer); } catch (e) { if (e instanceof TransportError && e.id === "InvalidChannel") { // this can happen if the device is connected // on a different channel (like another app) // in this case we just filter out the event continue; } throw e; } } log("apdu", "<= " + result.toString("hex")); return result; })).catch(e => { if (e && e.message && e.message.includes("write")) { this._emitDisconnect(e); throw new DisconnectedDeviceDuringOperation(e.message); } throw e; }); return b; }); this.device = device; this.deviceModel = typeof device.productId === "number" ? identifyUSBProductId(device.productId) : undefined; device.addEventListener("inputreport", this.onInputReport); } /** * Similar to create() except it will always display the device permission (even if some devices are already accepted). */ static request() { return __awaiter(this, void 0, void 0, function* () { const [device] = yield requestLedgerDevices(); return TransportWebHID.open(device); }); } /** * Similar to create() except it will never display the device permission (it returns a Promise<?Transport>, null if it fails to find a device). */ static openConnected() { return __awaiter(this, void 0, void 0, function* () { const devices = yield getLedgerDevices(); if (devices.length === 0) return null; return TransportWebHID.open(devices[0]); }); } /** * Create a Ledger transport with a HIDDevice */ static open(device) { return __awaiter(this, void 0, void 0, function* () { yield device.open(); const transport = new TransportWebHID(device); const onDisconnect = e => { if (device === e.device) { getHID().removeEventListener("disconnect", onDisconnect); transport._emitDisconnect(new DisconnectedDevice()); } }; getHID().addEventListener("disconnect", onDisconnect); return transport; }); } /** * Release the transport device */ close() { return __awaiter(this, void 0, void 0, function* () { yield this.exchangeBusyPromise; this.device.removeEventListener("inputreport", this.onInputReport); yield this.device.close(); }); } setScrambleKey() { } } /** * Check if WebUSB transport is supported. */ TransportWebHID.isSupported = isSupported; /** * List the WebUSB devices that was previously authorized by the user. */ TransportWebHID.list = getLedgerDevices; /** * Actively listen to WebUSB devices and emit ONE device * that was either accepted before, if not it will trigger the native permission UI. * * Important: it must be called in the context of a UI click! */ TransportWebHID.listen = (observer) => { let unsubscribed = false; getFirstLedgerDevice().then(device => { if (!device) { observer.error(new TransportOpenUserCancelled("Access denied to use Ledger device")); } else if (!unsubscribed) { const deviceModel = typeof device.productId === "number" ? identifyUSBProductId(device.productId) : undefined; observer.next({ type: "add", descriptor: device, deviceModel, }); observer.complete(); } }, error => { observer.error(new TransportOpenUserCancelled(error.message)); }); function unsubscribe() { unsubscribed = true; } return { unsubscribe, }; }; export default TransportWebHID; //# sourceMappingURL=TransportWebHID.js.map