forked from LedgerHQ/ledgerjs
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add @ledgerhq/hw-transport-node-hid-noevents with only node-hid dep
- Loading branch information
Showing
5 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[ignore] | ||
<PROJECT_ROOT>/lib | ||
|
||
[include] | ||
|
||
[libs] | ||
flow-typed | ||
|
||
[lints] | ||
|
||
[options] | ||
|
||
[strict] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<img src="https://user-images.githubusercontent.com/211411/34776833-6f1ef4da-f618-11e7-8b13-f0697901d6a8.png" height="100" /> | ||
|
||
[Github](https://github.com/LedgerHQ/ledgerjs/), | ||
[API Doc](http://ledgerhq.github.io/ledgerjs/), | ||
[Ledger Devs Slack](https://ledger-dev.slack.com/) | ||
|
||
## @ledgerhq/hw-transport-node-hid-noevents | ||
|
||
Allows to communicate with Ledger Hardware Wallets. | ||
|
||
**[Node]**/Electron **(HID)** – uses **only** `node-hid`. Does not provide USB events. | ||
|
||
## API | ||
|
||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> | ||
|
||
#### Table of Contents | ||
|
||
- [TransportNodeHidNoEvents](#transportnodehidnoevents) | ||
- [Parameters](#parameters) | ||
- [Examples](#examples) | ||
- [exchange](#exchange) | ||
- [Parameters](#parameters-1) | ||
- [close](#close) | ||
- [isSupported](#issupported) | ||
- [list](#list) | ||
- [listen](#listen) | ||
- [Parameters](#parameters-2) | ||
- [open](#open) | ||
- [Parameters](#parameters-3) | ||
|
||
### TransportNodeHidNoEvents | ||
|
||
**Extends Transport** | ||
|
||
node-hid Transport minimal implementation | ||
|
||
#### Parameters | ||
|
||
- `device` **HID.HID** | ||
|
||
#### Examples | ||
|
||
```javascript | ||
import TransportNodeHid from "@ledgerhq/hw-transport-node-hid-noevents"; | ||
... | ||
TransportNodeHid.create().then(transport => ...) | ||
``` | ||
|
||
#### exchange | ||
|
||
Exchange with the device using APDU protocol. | ||
|
||
##### Parameters | ||
|
||
- `apdu` **[Buffer](https://nodejs.org/api/buffer.html)** | ||
|
||
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Buffer](https://nodejs.org/api/buffer.html)>** a promise of apdu response | ||
|
||
#### close | ||
|
||
release the USB device. | ||
|
||
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void>** | ||
|
||
#### isSupported | ||
|
||
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** | ||
|
||
#### list | ||
|
||
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?>>** | ||
|
||
#### listen | ||
|
||
##### Parameters | ||
|
||
- `observer` **Observer<DescriptorEvent<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?>>** | ||
|
||
Returns **Subscription** | ||
|
||
#### open | ||
|
||
if path="" is not provided, the library will take the first device | ||
|
||
##### Parameters | ||
|
||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@ledgerhq/hw-transport-node-hid-noevents", | ||
"version": "4.48.0", | ||
"description": "Ledger Hardware Wallet Node implementation of the communication layer, using node-hid. without usb events", | ||
"keywords": [ | ||
"Ledger", | ||
"LedgerWallet", | ||
"hid", | ||
"node-hid", | ||
"NanoS", | ||
"Blue", | ||
"Hardware Wallet" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/LedgerHQ/ledgerjs" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/LedgerHQ/ledgerjs/issues" | ||
}, | ||
"homepage": "https://github.com/LedgerHQ/ledgerjs", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "lib/TransportNodeHid.js", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"@ledgerhq/devices": "^4.48.0", | ||
"@ledgerhq/errors": "^4.48.0", | ||
"@ledgerhq/hw-transport": "^4.48.0", | ||
"node-hid": "^0.7.7" | ||
}, | ||
"devDependencies": { | ||
"flow-bin": "^0.94.0", | ||
"flow-typed": "^2.5.1" | ||
}, | ||
"scripts": { | ||
"flow": "flow", | ||
"clean": "bash ../../script/clean.sh", | ||
"build": "bash ../../script/build.sh", | ||
"watch": "bash ../../script/watch.sh", | ||
"doc": "bash ../../script/doc.sh" | ||
} | ||
} |
185 changes: 185 additions & 0 deletions
185
packages/hw-transport-node-hid-noevents/src/TransportNodeHid.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
//@flow | ||
|
||
import HID from "node-hid"; | ||
import Transport from "@ledgerhq/hw-transport"; | ||
import type { | ||
Observer, | ||
DescriptorEvent, | ||
Subscription | ||
} from "@ledgerhq/hw-transport"; | ||
import { ledgerUSBVendorId } from "@ledgerhq/devices"; | ||
import hidFraming from "@ledgerhq/devices/lib/hid-framing"; | ||
import { identifyUSBProductId } from "@ledgerhq/devices"; | ||
import type { DeviceModel } from "@ledgerhq/devices"; | ||
import { TransportError, DisconnectedDevice } from "@ledgerhq/errors"; | ||
|
||
const filterInterface = device => | ||
["win32", "darwin"].includes(process.platform) | ||
? // $FlowFixMe | ||
device.usagePage === 0xffa0 | ||
: device.interface === 0; | ||
|
||
function getDevices(): Array<*> { | ||
// $FlowFixMe | ||
return HID.devices(ledgerUSBVendorId, 0x0).filter(filterInterface); | ||
} | ||
|
||
const isDisconnectedError = e => | ||
e && e.message && e.message.indexOf("HID") >= 0; | ||
|
||
/** | ||
* node-hid Transport minimal implementation | ||
* @example | ||
* import TransportNodeHid from "@ledgerhq/hw-transport-node-hid-noevents"; | ||
* ... | ||
* TransportNodeHid.create().then(transport => ...) | ||
*/ | ||
export default class TransportNodeHidNoEvents extends Transport<?string> { | ||
/** | ||
* | ||
*/ | ||
static isSupported = (): Promise<boolean> => | ||
Promise.resolve(typeof HID.HID === "function"); | ||
|
||
/** | ||
* | ||
*/ | ||
static list = (): Promise<(?string)[]> => | ||
Promise.resolve(getDevices().map(d => d.path)); | ||
|
||
/** | ||
*/ | ||
static listen = ( | ||
observer: Observer<DescriptorEvent<?string>> | ||
): Subscription => { | ||
getDevices().forEach(device => { | ||
const deviceModel = identifyUSBProductId(device.productId); | ||
observer.next({ | ||
type: "add", | ||
descriptor: device.path, | ||
deviceModel, | ||
device | ||
}); | ||
}); | ||
observer.complete(); | ||
return { unsubscribe: () => {} }; | ||
}; | ||
|
||
/** | ||
* if path="" is not provided, the library will take the first device | ||
*/ | ||
static async open(path: ?string) { | ||
if (path) { | ||
return Promise.resolve(new TransportNodeHidNoEvents(new HID.HID(path))); | ||
} | ||
const device = getDevices()[0]; | ||
if (!device) throw new TransportError("NoDevice", "NoDevice"); | ||
return Promise.resolve( | ||
new TransportNodeHidNoEvents(new HID.HID(device.path)) | ||
); | ||
} | ||
|
||
device: HID.HID; | ||
deviceModel: ?DeviceModel; | ||
|
||
channel = Math.floor(Math.random() * 0xffff); | ||
packetSize = 64; | ||
disconnected = false; | ||
|
||
constructor(device: HID.HID) { | ||
super(); | ||
this.device = device; | ||
const info = device.getDeviceInfo(); | ||
this.deviceModel = | ||
info && info.serialNumber | ||
? identifyUSBProductId(parseInt(info.serialNumber, 16)) | ||
: null; | ||
} | ||
|
||
setDisconnected = () => { | ||
if (!this.disconnected) { | ||
this.emit("disconnect"); | ||
this.disconnected = true; | ||
} | ||
}; | ||
|
||
writeHID = (content: Buffer): Promise<void> => { | ||
const data = [0x00]; | ||
for (let i = 0; i < content.length; i++) { | ||
data.push(content[i]); | ||
} | ||
try { | ||
this.device.write(data); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
if (isDisconnectedError(e)) { | ||
this.setDisconnected(); | ||
return Promise.reject(new DisconnectedDevice(e.message)); | ||
} | ||
return Promise.reject(e); | ||
} | ||
}; | ||
|
||
readHID = (): Promise<Buffer> => | ||
new Promise((resolve, reject) => | ||
this.device.read((e, res) => { | ||
if (!res) { | ||
return reject(new DisconnectedDevice()); | ||
} | ||
if (e) { | ||
if (isDisconnectedError(e)) { | ||
this.setDisconnected(); | ||
return reject(new DisconnectedDevice(e.message)); | ||
} | ||
reject(e); | ||
} else { | ||
const buffer = Buffer.from(res); | ||
resolve(buffer); | ||
} | ||
}) | ||
); | ||
|
||
/** | ||
* Exchange with the device using APDU protocol. | ||
* @param apdu | ||
* @returns a promise of apdu response | ||
*/ | ||
exchange = (apdu: Buffer): Promise<Buffer> => | ||
this.exchangeAtomicImpl(async () => { | ||
const { debug, channel, packetSize } = this; | ||
if (debug) { | ||
debug("=>" + apdu.toString("hex")); | ||
} | ||
|
||
const framing = hidFraming(channel, packetSize); | ||
|
||
// Write... | ||
const blocks = framing.makeBlocks(apdu); | ||
for (let i = 0; i < blocks.length; i++) { | ||
await this.writeHID(blocks[i]); | ||
} | ||
|
||
// Read... | ||
let result; | ||
let acc; | ||
while (!(result = framing.getReducedResult(acc))) { | ||
const buffer = await this.readHID(); | ||
acc = framing.reduceResponse(acc, buffer); | ||
} | ||
|
||
if (debug) { | ||
debug("<=" + result.toString("hex")); | ||
} | ||
return result; | ||
}); | ||
|
||
setScrambleKey() {} | ||
|
||
/** | ||
* release the USB device. | ||
*/ | ||
async close(): Promise<void> { | ||
await this.exchangeBusyPromise; | ||
this.device.close(); | ||
} | ||
} |