PC/SC (smart card) API bindings for Linux, MacOS, and Win32.
npm install pcsc-mini › NodeJS PC/SC API bindings for smart card access on Linux / MacOS / Win32
Docs ↗ | | Overview | | Prerequisites | | Installation | | Usage
``ts
import * as pcsc from "pcsc-mini";
const { CardDisposition, CardMode, ReaderStatus } = pcsc;
const client = new pcsc.Client()
.on("reader", onReader)
.start();
function onReader(reader: pcsc.Reader) {
reader.on("change", async status => {
if (!status.has(ReaderStatus.PRESENT)) return;
if (status.hasAny(ReaderStatus.MUTE, ReaderStatus.IN_USE)) return;
const card = await reader.connect(CardMode.SHARED);
console.log(${await card.state()});
const resTx = await card.transmit(
Uint8Array.of(0xca, 0xfe, 0xf0, 0x0d)
);
console.log(resTx);
const codeFeatures = pcsc.controlCode(3400);
const features = await card.control(codeFeatures);
console.log(features);
await card.disconnect(CardDisposition.RESET);
client.stop();
process.exit(0);
});
}
`
pcsc-mini provides NodeJS bindings to native PC/SC (Personal Computer/Smart Card) APIs:pcsc-lite
- on Linux and MacOS*winscard
- on Windows.
* MacOS has a separate implementation built on the CryptoTokenKit API.
Pre-built binary packages are available for the following targets.
These are installed as optional dependencies of the main pcsc-mini package (e.g. @pcsc-mini/linux-x86_64-gnu).
| OS | arm64 | x86 | x86_64 |
|------------------------------|:-----:|:-----:|:------:|
| Linux ( gnu ) | ✅ | ☑️ | ✅ |Linux ( musl )
| 1 | ✅ | ☑️ | ✅ |MacOS
| | ✅ | N/A | ☑️ |Windows
| 2 | ☑️ | ⬜️ | ✅ |
✅ Tested & verified •
☑️ Not tested •
⬜️ Not available
1 During testing on Alpine, the PCSC server daemon needed to be started after a reader was connected for detection/monitoring to work and required a restart whenever a reader was disconnected and reconnected.
2 Windows support is limited to a few JS runtimes at the moment (Node.js, Bun, Electron). If additional runtimes are needed, please feel free to open an issue to request it.
| Runtime | Supported Versions |
|------------------------|-----------------------------------------------|
| NodeJS | v16.x.x, v18.x.x, v20.x.x, v22.x.x v24.x.x |Tested with v1.2.12 (may work with earlier)
| OTHERS: | |
| Bun | |Tested with v2.3.1 (may work with earlier)
| Deno | |v15.0.0+ (Tested up to v36.2.0)
| Electron | |
Required packages:
- ccidpcsc-lite
- pcsc-lite-libs
-
`sh`
doas apk add ccid pcsc-lite pcsc-lite-libs`
To run the server daemon:sh`
doas rc-service pcscd start
Required packages:
- libpcsclite1pcscd
-
`sh`
sudo apt install libpcsclite1 pcscd`
To run the server daemon:sh`
sudo systemctl start pcscd
N/A :: MacOS and Windows come pre-installed with smart card support. No additional installation needed.
Bun
`sh`
bun add pcsc-mini
Deno
`sh`
deno add npm:pcsc-mini
npm
`sh`
npm i pcsc-mini
pnpm
`sh`
pnpm add pcsc-mini
`ts
import * as pcsc from "pcsc-mini";
const { CardDisposition, CardMode, ReaderStatus } = pcsc;
// The Client emits a "reader" event for each detected device.
const client = new pcsc.Client()
.on("reader", onReader)
.on("error", onError)
.start();
function onError(err: pcsc.Err) {
console.error("Unexpected PCSC error:", err);
client.stop();
// [ Log and exit / attempt start() retries with backoff / etc... ]
};
function onReader(reader: pcsc.Reader) {
let card: pcsc.Card | undefined;
console.log(Reader detected: ${reader});
// Each reader emits a "change" event on every reader state change.
reader.on("change", async status => {
if (status.hasAny(ReaderStatus.MUTE, ReaderStatus.IN_USE)) return;
if (!status.has(ReaderStatus.PRESENT)) {
void card?.disconnect(CardDisposition.RESET);
card = undefined;
return;
}
try {
if (!card) card = await reader.connect(CardMode.SHARED);
// Transmit Uint8Array (or NodeJS Buffer) data:
const res = await card.transmit(
Uint8Array.of(0xca, 0xfe, 0xf0, 0x0d)
);
// Use Uint8Array response directly, or via DataView/Buffer:
const vw = new DataView(res.buffer, res.byteOffset, res.length);
const tag = vw.getUint8(0);
const len = vw.getUint16(2);
const val = new Uint8Array(res.buffer, 4, len);
// ...
} catch (err) {
console.error("Card error:", err);
}
});
// "disconnect" is emitted when a reader is no longer detected.
//
// All event listeners will be removed from the now-invalid reader.
// Any reader/card-related state should be disposed of here.
reader.on("disconnect", async () => {
void card?.disconnect(CardDisposition.RESET);
card = undefined;
});
}
`
> [!TIP]
>
> See the E2E test application for more involved usage and error handling.
| | Minimum Version | Recommended Version |
|--------------------------|-----------------|------------------------------------|
| Zig | v0.15.0 | See .zigversion |
| NodeJS | v24.0.0 | See .nvmrc |
| pnpm | v10.0.0 | See package.json |
| OPTIONAL: | | |
| Bun | v1.2.12 | |
| Deno | v2.3.1 | |
#### Linux
See Prerequisites section above for a list of runtime prerequisites.
Other relevant development libraries (e.g. libpcsclite-dev on Debian-based distros) are included in the pcsc dependency. No additional installation needed.
#### MacOS
N/A :: Required MacOS Framework .tbds are included in the pcsc dependency. No additional installation needed.
#### Windows
N/A :: Required DLLs are shipped with the Zig compiler. No additional installation needed.
#### Building the dev addon binary:
This will output an lib/addon.node file to enable unit testing. Runs automatically when running the unit tests.
`sh`
zig build
#### Running Zig and NodeJS unit tests:
`sh`
zig build test
##### Or individually:
`sh`
zig build test:node -- --watch`sh`
zig build test:zig --watch
#### Running the E2E test application:
This enables testing basic operations against real devices (supporting up to 4 simultaneously connected readers) to verify functionality not testable via unit tests.
`sh`
zig build e2e
#### Generating the NPM packages:
This will output the final NPM package directories to ./zig-out/npm_packages.
`sh``
zig build packages