Skip to content

Commit

Permalink
Add @ledgerhq/hw-transport-node-hid-noevents with only node-hid dep
Browse files Browse the repository at this point in the history
  • Loading branch information
gre committed Apr 1, 2019
1 parent c98c159 commit 88bc234
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Welcome to Ledger's JavaScript libraries.
- [![npm](https://img.shields.io/npm/v/@ledgerhq/hw-transport-webusb.svg)](https://www.npmjs.com/package/@ledgerhq/hw-transport-webusb) [@ledgerhq/hw-transport-webusb](./packages/hw-transport-webusb) **[Web]** **(WebUSB)** (experimental) – WebUSB [check browser support](https://caniuse.com/webusb).
- [![npm](https://img.shields.io/npm/v/@ledgerhq/hw-transport-web-ble.svg)](https://www.npmjs.com/package/@ledgerhq/hw-transport-web-ble) [@ledgerhq/hw-transport-web-ble](./packages/hw-transport-web-ble) **[Web]** **(Bluetooth)**[check browser support](https://caniuse.com/web-bluetooth).
- [![npm](https://img.shields.io/npm/v/@ledgerhq/hw-transport-node-hid.svg)](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-hid) [@ledgerhq/hw-transport-node-hid](./packages/hw-transport-node-hid) **[Node]**/Electron **(HID)** – uses `node-hid` and `usb`.
- [![npm](https://img.shields.io/npm/v/@ledgerhq/hw-transport-node-hid-noevents.svg)](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-hid-noevents) [@ledgerhq/hw-transport-node-hid-noevents](./packages/hw-transport-node-hid-noevents) **[Node]**/Electron **(HID)** – uses **only** `node-hid`. Does not provide USB events.
- [![npm](https://img.shields.io/npm/v/@ledgerhq/react-native-hw-transport-ble.svg)](https://www.npmjs.com/package/@ledgerhq/react-native-hw-transport-ble) [@ledgerhq/react-native-hw-transport-ble](./packages/react-native-hw-transport-ble) **[React Native]** **(Bluetooth)** – uses `react-native-ble-plx`
- [![npm](https://img.shields.io/npm/v/@ledgerhq/react-native-hid.svg)](https://www.npmjs.com/package/@ledgerhq/react-native-hid) [@ledgerhq/react-native-hid](./packages/react-native-hid) **[React Native]** **(HID)** _Android_ – Ledger's native implementation
- [![npm](https://img.shields.io/npm/v/@ledgerhq/hw-transport-http.svg)](https://www.npmjs.com/package/@ledgerhq/hw-transport-http) [@ledgerhq/hw-transport-http](./packages/hw-transport-http) **[DEV only]** universal HTTP channel. **NOT for PROD**.
Expand Down
13 changes: 13 additions & 0 deletions packages/hw-transport-node-hid-noevents/.flowconfig
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]
88 changes: 88 additions & 0 deletions packages/hw-transport-node-hid-noevents/README.md
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)&lt;[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)&lt;void>**

#### isSupported

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[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)&lt;[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?>>**

#### listen

##### Parameters

- `observer` **Observer&lt;DescriptorEvent&lt;[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)?**
44 changes: 44 additions & 0 deletions packages/hw-transport-node-hid-noevents/package.json
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 packages/hw-transport-node-hid-noevents/src/TransportNodeHid.js
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();
}
}

0 comments on commit 88bc234

Please sign in to comment.