diff --git a/CHANGELOG.md b/CHANGELOG.md index e164391..22e526f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to `@railgun-community/ledger-client` are documented here. This project is pre-1.0 and experimental; expect breaking changes on minor versions. +## 0.4.1 — 2026-08-21 + +### Fixed + +- **`NodeHIDTransport` is reachable from consumers.** The package `exports` map previously + declared only `"."`, so the deep path every Node consumer was directed to + (`dist/core/transport/nodehid-transport.js`) was blocked by Node and TypeScript + (`ERR_MODULE_NOT_FOUND` / `TS2307`), and the class was not re-exported from the root either. + Both routes the transport factory's own error message suggests — direct import and + `transportFactory` injection — were therefore impossible. Added a `./node` subpath export + and a root re-export. Use `@railgun-community/ledger-client/node` in bare Node ESM; the root + barrel reaches `@ledgerhq/errors@6.32.0`, whose `lib-es` build Node's ESM resolver rejects. +- **`@ledgerhq/hw-transport-node-hid` declared as an optional peer dependency**, matching the + existing treatment of the WebHID and Web BLE transports. It was a devDependency only, so + consumers were never told to install it. + ## 0.4.0 — 2026-07-24 Grows the CLEAR_SIGN transact surface from the 0.3.0 builders into full, engine-ready diff --git a/docs/api/transport.md b/docs/api/transport.md index a7a1946..7bf37f9 100644 --- a/docs/api/transport.md +++ b/docs/api/transport.md @@ -48,12 +48,36 @@ loaded lazily on `connect()`). `'nodehid'` throws a directional `HWError` — No - a **user gesture** to trigger the device-selection prompt (call `connect()` from a click) - not embedded in an iframe that blocks the `hid` Permissions-Policy -## Node — scripts & tests +## Node — NodeHIDTransport -The bundled scripts (`install-app.ts`, `test-*-live.ts`) use a Node HID transport internally -over `@ledgerhq/hw-transport-node-hid`. For unit tests, inject a **mock** transport through -the controller's `transportFactory` (see [controller.md](./controller.md#controller-options)) -rather than touching real hardware. +`NodeHIDTransport` is constructed directly — the browser `createTransport` factory does not +build it. Import it from the **`/node` subpath**: + +```ts +import { NodeHIDTransport } from '@railgun-community/ledger-client/node'; + +const transport = new NodeHIDTransport(); +await transport.connect(); +``` + +It is also re-exported from the package root, which is the convenient form under a bundler. +**In bare Node ESM, use the subpath.** The root barrel statically pulls in +`@ledgerhq/hw-transport-webhid` and `@ledgerhq/hw-transport`, which reach +`@ledgerhq/errors@6.32.0` — that package's `lib-es` build uses an extensionless relative import +that Node's ESM resolver rejects (`ERR_MODULE_NOT_FOUND` on `lib-es/helpers`). Bundlers resolve +it; bare `node` does not. The `/node` subpath sidesteps it entirely — `nodehid-transport` has no +static `@ledgerhq` imports at all. + +It requires the optional peer dep `@ledgerhq/hw-transport-node-hid` (native `node-hid` +bindings), loaded lazily on `connect()` — so importing it in a browser bundle pulls in nothing +Node-specific, and consumers that never use it don't need the dep installed. + +To use it through the controller, inject it: +`createLedgerController({ transportFactory: () => new NodeHIDTransport() })`. + +The bundled scripts (`install-app.ts`, `test-*-live.ts`) use this transport internally. For unit +tests, inject a **mock** transport through the controller's `transportFactory` (see +[controller.md](./controller.md#controller-options)) rather than touching real hardware. ## Choosing / injecting a transport diff --git a/package.json b/package.json index 16ff3f7..2ec1b2d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,12 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" - } + }, + "./node": { + "types": "./dist/core/transport/nodehid-transport.d.ts", + "import": "./dist/core/transport/nodehid-transport.js" + }, + "./package.json": "./package.json" }, "sideEffects": false, "files": [ @@ -61,6 +66,7 @@ "peerDependencies": { "@ledgerhq/hw-app-eth": "^6.41.0", "@ledgerhq/hw-transport": "^6.31.0", + "@ledgerhq/hw-transport-node-hid": "^6.32.1", "@ledgerhq/hw-transport-web-ble": "^6.30.0", "@ledgerhq/hw-transport-webhid": "^6.30.0" }, @@ -68,6 +74,9 @@ "@ledgerhq/hw-app-eth": { "optional": true }, + "@ledgerhq/hw-transport-node-hid": { + "optional": true + }, "@ledgerhq/hw-transport-web-ble": { "optional": true }, diff --git a/src/core/transport/transport-factory.ts b/src/core/transport/transport-factory.ts index c67dd32..56a8955 100644 --- a/src/core/transport/transport-factory.ts +++ b/src/core/transport/transport-factory.ts @@ -27,7 +27,7 @@ export function createTransport(config?: TransportConfig): HWTransport { case 'nodehid': throw new HWError( HWErrorCode.TRANSPORT_NOT_AVAILABLE, - 'Node HID transport is Node-only and not constructed by the browser factory. Import NodeHIDTransport directly in a Node context, or inject it via the controller transportFactory option.', + 'Node HID transport is Node-only and not constructed by the browser factory. Import it in a Node context (`import { NodeHIDTransport } from "@railgun-community/ledger-client/node"`), or inject it via the controller transportFactory option.', ); case 'ble': diff --git a/src/index.ts b/src/index.ts index 6e7e0cf..2b3b0a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -328,6 +328,7 @@ export { export { createTransport } from './core/transport/transport-factory.js'; export { WebHIDTransport } from './core/transport/webhid-transport.js'; export { WebBLETransport } from './core/transport/web-ble-transport.js'; +export { NodeHIDTransport } from './core/transport/nodehid-transport.js'; // ─── Device-state recovery ─────────────────────────────────────────────────── export { clearDeviceState } from './core/transport/clear-state.js';