Skip to content

fix(transport): make NodeHIDTransport reachable from consumers - #31

Merged
zy0n merged 1 commit into
mainfrom
fix/node-transport-exports
Aug 21, 2026
Merged

fix(transport): make NodeHIDTransport reachable from consumers#31
zy0n merged 1 commit into
mainfrom
fix/node-transport-exports

Conversation

@zy0n

@zy0n zy0n commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

NodeHIDTransport shipped in the package but no consumer could reach it.

The exports map declared only ".", so the deep path that transport-factory's own
error message directs Node consumers to — dist/core/transport/nodehid-transport.js
was blocked by Node (ERR_PACKAGE_PATH_NOT_EXPORTED) and TypeScript (TS2307). The
class was not re-exported from the root either.

That made both remedies the factory suggests impossible: you could not import it
directly, and you could not construct one in order to inject it via transportFactory.
The only route left for a Node consumer was reimplementing HWTransport against
node-hid — duplicating a transport this package already ships and tests.

Fix

  • ./node subpath export@railgun-community/ledger-client/node
  • root re-export of NodeHIDTransport, alongside WebHIDTransport / WebBLETransport
  • @ledgerhq/hw-transport-node-hid declared as an optional peer dependency. It was a
    devDependency only, so consumers were never told to install it, while WebHID and Web BLE
    were already declared this way.
  • factory error message and docs/api/transport.md now name the supported import path

Why both a subpath and a root export

The root re-export alone does not unblock a Node consumer. Measured from a real consumer
in bare Node ESM:

SUBPATH OK  -> NodeHIDTransport = function NodeHIDTransport
ROOT FAIL: ERR_MODULE_NOT_FOUND .../@ledgerhq/errors/lib-es/helpers

The root barrel statically pulls @ledgerhq/hw-transport-webhid and
@ledgerhq/hw-transport, which reach @ledgerhq/errors@6.32.0, whose lib-es/index.js
uses an extensionless relative import that Node's ESM resolver rejects. Bundlers resolve
it; bare node does not. This is pre-existing — webhid-transport.js and
ledger-transport-adapter.js each fail identically on their own, independent of this
change.

The subpath sidesteps it: nodehid-transport has no static @ledgerhq imports at all
(only ./types.js, ../errors.js, ./apdu-wire.js). That is also why the root
re-export stays browser-safe — the node-hid dependency is behind a dynamic import
inside connect(), the same shape WebBLETransport already uses.

Verification

  • yarn lint, yarn typecheck, yarn build clean; 547 tests / 38 files pass
  • packed the tarball and installed it into a clean consumer — nodehid-transport.{js,d.ts}
    present in package/dist/, and import ... from '@railgun-community/ledger-client/node'
    resolves at runtime
  • consumer-side TypeScript resolution confirmed under --module nodenext

Follow-up, not in this PR

Making webhid-transport and ledger-transport-adapter lazy would fix the root barrel
under bare Node ESM. That is a behavioral change with real risk and does not belong in a
packaging fix.

The exports map declared only ".", so the deep path the transport factory's
own error message directs Node consumers to — dist/core/transport/
nodehid-transport.js — was blocked by both Node (ERR_PACKAGE_PATH_NOT_EXPORTED)
and TypeScript (TS2307). The class was not re-exported from the root either, so
neither remedy that message suggests was possible: you could not import it
directly, and you could not construct one to inject via transportFactory. The
only route left was reimplementing HWTransport against node-hid.

Add a ./node subpath export and a root re-export. The subpath is the one that
works in bare Node ESM: the root barrel statically pulls hw-transport-webhid
and hw-transport, which reach @ledgerhq/errors@6.32.0, whose lib-es build uses
an extensionless relative import that Node's ESM resolver rejects. Bundlers
resolve it, node does not. nodehid-transport has no static @LedgerHQ imports at
all, so the subpath sidesteps it and the root re-export stays browser-safe —
the node-hid dependency is behind a dynamic import inside connect().

Also declare @ledgerhq/hw-transport-node-hid as an optional peer dependency. It
was a devDependency only, so consumers were never told to install it, while the
WebHID and Web BLE transports were already declared this way.
@zy0n
zy0n merged commit 606c381 into main Aug 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant