smartcard 1.0.46 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2018 tomkp
3
+ Copyright (c) 2025 Tom KP
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,453 @@
1
+ # smartcard
2
+
3
+ ```
4
+ ╭────────────────────────────────────────╮
5
+ │ │
6
+ │ ╭───────────╮ │
7
+ │ │ ▄▄▄ ▄▄▄▄▄ │ │
8
+ │ │ ███ ▄▄▄▄▄ │ │
9
+ │ │ ▀▀▀ ▀▀▀▀▀ │ │
10
+ │ ╰───────────╯ │
11
+ │ │
12
+ │ ░░░░░░░░░░░░ │
13
+ │ ░░░░░░░░░░░░ │
14
+ ╰────────────────────────────────────────╯
15
+ ```
16
+
17
+ Stable PC/SC smart card bindings for Node.js.
18
+
19
+ Works with Node.js 12+ without recompilation. Built on N-API for long-term stability.
20
+
21
+ ## Getting Started
22
+
23
+ ### 1. Install the package
24
+
25
+ ```bash
26
+ npm install smartcard
27
+ ```
28
+
29
+ ### 2. Platform setup
30
+
31
+ **macOS/Windows**: Ready to go - no additional setup needed.
32
+
33
+ **Linux**:
34
+ ```bash
35
+ # Install PC/SC libraries
36
+ sudo apt-get install libpcsclite-dev pcscd # Debian/Ubuntu
37
+ sudo dnf install pcsc-lite-devel pcsc-lite # Fedora/RHEL
38
+
39
+ # Start the daemon
40
+ sudo systemctl start pcscd
41
+ ```
42
+
43
+ ### 3. Connect a reader and run your first script
44
+
45
+ ```javascript
46
+ const { Devices } = require('smartcard');
47
+
48
+ const devices = new Devices();
49
+
50
+ devices.on('card-inserted', async ({ reader, card }) => {
51
+ console.log(`Card detected in ${reader.name}`);
52
+ console.log(`ATR: ${card.atr.toString('hex')}`);
53
+
54
+ // Get card UID (works with most contactless cards)
55
+ const response = await card.transmit([0xFF, 0xCA, 0x00, 0x00, 0x00]);
56
+ console.log(`UID: ${response.slice(0, -2).toString('hex')}`);
57
+ });
58
+
59
+ devices.on('error', (err) => console.error(err.message));
60
+
61
+ devices.start();
62
+ ```
63
+
64
+ Run it:
65
+ ```bash
66
+ node app.js
67
+ # Tap a card on your reader...
68
+ # Card detected in ACS ACR122U
69
+ # ATR: 3b8f8001804f0ca0000003060300030000000068
70
+ # UID: 04a23b7a
71
+ ```
72
+
73
+ ## Recommended Hardware
74
+
75
+ ### Readers
76
+
77
+ | Reader | Type | Notes |
78
+ |--------|------|-------|
79
+ | **ACR122U** | USB contactless | Affordable, widely available. Great for getting started. |
80
+ | **ACR1252U** | USB dual-interface | Supports both contactless and contact cards. |
81
+ | **SCM SCR35xx** | USB contact | Tested with SCR35xx v2.0. Good for contact smart cards. |
82
+ | **HID Omnikey 5427** | USB contactless | Enterprise-grade, faster reads. |
83
+ | **Identiv uTrust 3700F** | USB contactless | Compact, reliable. |
84
+
85
+ Any PC/SC compatible reader should work. The library uses standard PC/SC APIs.
86
+
87
+ ### Cards
88
+
89
+ | Card Type | Interface | Notes |
90
+ |-----------|-----------|-------|
91
+ | MIFARE Classic 1K/4K | Contactless | Most common NFC cards |
92
+ | MIFARE Ultralight / NTAG | Contactless | Stickers, wristbands, keyfobs |
93
+ | MIFARE DESFire | Contactless | Higher security applications |
94
+ | ISO 14443-4 | Contactless | Generic contactless smart cards |
95
+ | ISO 7816 | Contact | Standard contact smart cards (SIM, bank cards, ID cards) |
96
+
97
+ ## Features
98
+
99
+ - **ABI Stable**: Works across Node.js versions without recompilation
100
+ - **Async/Promise-based**: Non-blocking card operations
101
+ - **Event-driven API**: High-level `Devices` class with EventEmitter
102
+ - **TypeScript support**: Full type definitions included
103
+ - **Cross-platform**: Windows, macOS, and Linux
104
+
105
+ ## More Examples
106
+
107
+ ### High-Level API (Event-Driven)
108
+
109
+ ```javascript
110
+ const { Devices } = require('smartcard');
111
+
112
+ const devices = new Devices();
113
+
114
+ devices.on('reader-attached', (reader) => {
115
+ console.log(`Reader attached: ${reader.name}`);
116
+ });
117
+
118
+ devices.on('reader-detached', (reader) => {
119
+ console.log(`Reader detached: ${reader.name}`);
120
+ });
121
+
122
+ devices.on('card-inserted', async ({ reader, card }) => {
123
+ console.log(`Card inserted in ${reader.name}`);
124
+ console.log(` ATR: ${card.atr.toString('hex')}`);
125
+
126
+ // Send APDU command
127
+ try {
128
+ const response = await card.transmit([0xFF, 0xCA, 0x00, 0x00, 0x00]);
129
+ console.log(` UID: ${response.slice(0, -2).toString('hex')}`);
130
+ } catch (err) {
131
+ console.error('Transmit error:', err.message);
132
+ }
133
+ });
134
+
135
+ devices.on('card-removed', ({ reader }) => {
136
+ console.log(`Card removed from ${reader.name}`);
137
+ });
138
+
139
+ devices.on('error', (err) => {
140
+ console.error('Error:', err.message);
141
+ });
142
+
143
+ // Start monitoring
144
+ devices.start();
145
+
146
+ // Stop on exit
147
+ process.on('SIGINT', () => {
148
+ devices.stop();
149
+ process.exit();
150
+ });
151
+ ```
152
+
153
+ ### Low-Level API (Direct PC/SC)
154
+
155
+ ```javascript
156
+ const {
157
+ Context,
158
+ SCARD_SHARE_SHARED,
159
+ SCARD_PROTOCOL_T0,
160
+ SCARD_PROTOCOL_T1,
161
+ SCARD_LEAVE_CARD
162
+ } = require('smartcard');
163
+
164
+ async function main() {
165
+ // Create PC/SC context
166
+ const ctx = new Context();
167
+ console.log('Context valid:', ctx.isValid);
168
+
169
+ // List readers
170
+ const readers = ctx.listReaders();
171
+ console.log('Readers:', readers.map(r => r.name));
172
+
173
+ if (readers.length === 0) {
174
+ console.log('No readers found');
175
+ ctx.close();
176
+ return;
177
+ }
178
+
179
+ const reader = readers[0];
180
+ console.log(`Using reader: ${reader.name}`);
181
+ console.log(` State: ${reader.state}`);
182
+
183
+ // Connect to card
184
+ try {
185
+ const card = await reader.connect(
186
+ SCARD_SHARE_SHARED,
187
+ SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1
188
+ );
189
+ console.log(`Connected, protocol: ${card.protocol}`);
190
+
191
+ // Get card status
192
+ const status = card.getStatus();
193
+ console.log(` ATR: ${status.atr.toString('hex')}`);
194
+
195
+ // Send APDU (Get UID for contactless cards)
196
+ const response = await card.transmit(Buffer.from([0xFF, 0xCA, 0x00, 0x00, 0x00]));
197
+ console.log(` Response: ${response.toString('hex')}`);
198
+
199
+ // Disconnect
200
+ card.disconnect(SCARD_LEAVE_CARD);
201
+ } catch (err) {
202
+ console.error('Card error:', err.message);
203
+ }
204
+
205
+ // Close context
206
+ ctx.close();
207
+ }
208
+
209
+ main();
210
+ ```
211
+
212
+ ### Waiting for Card Changes
213
+
214
+ ```javascript
215
+ const { Context } = require('smartcard');
216
+
217
+ async function waitForCard() {
218
+ const ctx = new Context();
219
+ const readers = ctx.listReaders();
220
+
221
+ if (readers.length === 0) {
222
+ console.log('No readers found');
223
+ ctx.close();
224
+ return;
225
+ }
226
+
227
+ console.log('Waiting for card...');
228
+
229
+ // Wait for state change (timeout: 30 seconds)
230
+ const changes = await ctx.waitForChange(readers, 30000);
231
+
232
+ if (changes === null) {
233
+ console.log('Cancelled');
234
+ } else if (changes.length === 0) {
235
+ console.log('Timeout');
236
+ } else {
237
+ for (const change of changes) {
238
+ if (change.changed) {
239
+ console.log(`${change.name}: state changed to ${change.state}`);
240
+ if (change.atr) {
241
+ console.log(` ATR: ${change.atr.toString('hex')}`);
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ ctx.close();
248
+ }
249
+
250
+ waitForCard();
251
+ ```
252
+
253
+ ## API Reference
254
+
255
+ ### Context
256
+
257
+ The low-level PC/SC context.
258
+
259
+ ```typescript
260
+ class Context {
261
+ constructor();
262
+ readonly isValid: boolean;
263
+ listReaders(): Reader[];
264
+ waitForChange(readers?: Reader[], timeout?: number): Promise<ReaderState[] | null>;
265
+ cancel(): void;
266
+ close(): void;
267
+ }
268
+ ```
269
+
270
+ ### Reader
271
+
272
+ Represents a smart card reader.
273
+
274
+ ```typescript
275
+ interface Reader {
276
+ readonly name: string;
277
+ readonly state: number;
278
+ readonly atr: Buffer | null;
279
+ connect(shareMode?: number, protocol?: number): Promise<Card>;
280
+ }
281
+ ```
282
+
283
+ ### Card
284
+
285
+ Represents a connected smart card.
286
+
287
+ ```typescript
288
+ interface Card {
289
+ readonly protocol: number;
290
+ readonly connected: boolean;
291
+ readonly atr: Buffer | null;
292
+ transmit(command: Buffer | number[]): Promise<Buffer>;
293
+ control(code: number, data?: Buffer): Promise<Buffer>;
294
+ getStatus(): { state: number; protocol: number; atr: Buffer };
295
+ disconnect(disposition?: number): void;
296
+ reconnect(shareMode?: number, protocol?: number, init?: number): number;
297
+ }
298
+ ```
299
+
300
+ ### Devices
301
+
302
+ High-level event-driven API.
303
+
304
+ ```typescript
305
+ class Devices extends EventEmitter {
306
+ start(): void;
307
+ stop(): void;
308
+ listReaders(): Reader[];
309
+
310
+ on(event: 'reader-attached', listener: (reader: Reader) => void): this;
311
+ on(event: 'reader-detached', listener: (reader: Reader) => void): this;
312
+ on(event: 'card-inserted', listener: (event: { reader: Reader; card: Card }) => void): this;
313
+ on(event: 'card-removed', listener: (event: { reader: Reader; card: Card | null }) => void): this;
314
+ on(event: 'error', listener: (error: Error) => void): this;
315
+ }
316
+ ```
317
+
318
+ ### Constants
319
+
320
+ ```javascript
321
+ // Share modes
322
+ SCARD_SHARE_EXCLUSIVE // Exclusive access
323
+ SCARD_SHARE_SHARED // Shared access (default)
324
+ SCARD_SHARE_DIRECT // Direct access to reader
325
+
326
+ // Protocols
327
+ SCARD_PROTOCOL_T0 // T=0 protocol
328
+ SCARD_PROTOCOL_T1 // T=1 protocol
329
+ SCARD_PROTOCOL_RAW // Raw protocol
330
+
331
+ // Disposition (for disconnect)
332
+ SCARD_LEAVE_CARD // Leave card as-is
333
+ SCARD_RESET_CARD // Reset the card
334
+ SCARD_UNPOWER_CARD // Power down the card
335
+ SCARD_EJECT_CARD // Eject the card
336
+
337
+ // State flags
338
+ SCARD_STATE_PRESENT // Card is present
339
+ SCARD_STATE_EMPTY // No card in reader
340
+ SCARD_STATE_CHANGED // State has changed
341
+ // ... and more
342
+ ```
343
+
344
+ ## Common APDU Commands
345
+
346
+ ```javascript
347
+ // Get UID (for contactless cards via PC/SC pseudo-APDU)
348
+ const GET_UID = [0xFF, 0xCA, 0x00, 0x00, 0x00];
349
+
350
+ // Select by AID
351
+ const SELECT_AID = [0x00, 0xA4, 0x04, 0x00, /* length */, /* AID bytes */];
352
+
353
+ // Read binary
354
+ const READ_BINARY = [0x00, 0xB0, /* P1: offset high */, /* P2: offset low */, /* Le */];
355
+ ```
356
+
357
+ ## Error Handling
358
+
359
+ ```javascript
360
+ const { PCSCError, CardRemovedError, TimeoutError } = require('smartcard');
361
+
362
+ try {
363
+ const response = await card.transmit([0x00, 0xA4, 0x04, 0x00]);
364
+ } catch (err) {
365
+ if (err instanceof CardRemovedError) {
366
+ console.log('Card was removed');
367
+ } else if (err instanceof TimeoutError) {
368
+ console.log('Operation timed out');
369
+ } else if (err instanceof PCSCError) {
370
+ console.log(`PC/SC error: ${err.message} (code: ${err.code})`);
371
+ } else {
372
+ throw err;
373
+ }
374
+ }
375
+ ```
376
+
377
+ ## Troubleshooting
378
+
379
+ ### "No readers available"
380
+ - Ensure a PC/SC compatible reader is connected
381
+ - On Linux, ensure `pcscd` service is running: `sudo systemctl status pcscd`
382
+
383
+ ### "PC/SC service not running"
384
+ - Linux: `sudo systemctl start pcscd`
385
+ - Windows: Check "Smart Card" service is running
386
+
387
+ ### "Sharing violation"
388
+ - Another application has exclusive access to the card
389
+ - Close other smart card applications
390
+
391
+ ### Build errors on Linux
392
+ - Install development headers: `sudo apt-get install libpcsclite-dev`
393
+
394
+ ## Migrating from v1.x
395
+
396
+ Version 2.0 is a complete rewrite using N-API for stability across Node.js versions.
397
+
398
+ ### Breaking Changes
399
+
400
+ | v1.x | v2.x |
401
+ |------|------|
402
+ | `device-activated` event | `reader-attached` event |
403
+ | `device-deactivated` event | `reader-detached` event |
404
+ | `event.device` | `reader` (passed directly) |
405
+ | `device.on('card-inserted')` | `devices.on('card-inserted')` |
406
+ | `card.issueCommand()` | `card.transmit()` |
407
+
408
+ ### Migration Example
409
+
410
+ **v1.x:**
411
+ ```javascript
412
+ const { Devices } = require('smartcard');
413
+ const devices = new Devices();
414
+
415
+ devices.on('device-activated', event => {
416
+ const device = event.device;
417
+ device.on('card-inserted', event => {
418
+ const card = event.card;
419
+ card.issueCommand(new CommandApdu({...}));
420
+ });
421
+ });
422
+ ```
423
+
424
+ **v2.x:**
425
+ ```javascript
426
+ const { Devices } = require('smartcard');
427
+ const devices = new Devices();
428
+
429
+ devices.on('reader-attached', reader => {
430
+ console.log('Reader:', reader.name);
431
+ });
432
+
433
+ devices.on('card-inserted', ({ reader, card }) => {
434
+ const response = await card.transmit([0x00, 0xA4, 0x04, 0x00]);
435
+ });
436
+
437
+ devices.start();
438
+ ```
439
+
440
+ ### Key Improvements in v2.x
441
+ - Works on Node.js 12, 14, 16, 18, 20, 22, 24+ without recompilation
442
+ - Native N-API bindings (no more NAN compatibility issues)
443
+ - Simpler flat event model
444
+ - Full TypeScript definitions
445
+ - Promise-based async API
446
+
447
+ ## License
448
+
449
+ MIT
450
+
451
+ ## Related Projects
452
+
453
+ - [nfc-pcsc](https://www.npmjs.com/package/nfc-pcsc) - NFC library built on smartcard
package/binding.gyp ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "targets": [{
3
+ "target_name": "smartcard_napi",
4
+ "cflags!": ["-fno-exceptions"],
5
+ "cflags_cc!": ["-fno-exceptions"],
6
+ "sources": [
7
+ "src/addon.cpp",
8
+ "src/pcsc_context.cpp",
9
+ "src/pcsc_reader.cpp",
10
+ "src/pcsc_card.cpp",
11
+ "src/async_workers.cpp",
12
+ "src/reader_monitor.cpp"
13
+ ],
14
+ "include_dirs": [
15
+ "<!@(node -p \"require('node-addon-api').include\")"
16
+ ],
17
+ "defines": [
18
+ "NAPI_VERSION=8",
19
+ "NAPI_CPP_EXCEPTIONS"
20
+ ],
21
+ "conditions": [
22
+ ["OS=='win'", {
23
+ "libraries": ["-lwinscard"],
24
+ "msvs_settings": {
25
+ "VCCLCompilerTool": {
26
+ "ExceptionHandling": 1
27
+ }
28
+ }
29
+ }],
30
+ ["OS=='mac'", {
31
+ "libraries": ["-framework PCSC"],
32
+ "xcode_settings": {
33
+ "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
34
+ "CLANG_CXX_LIBRARY": "libc++",
35
+ "MACOSX_DEPLOYMENT_TARGET": "10.15"
36
+ }
37
+ }],
38
+ ["OS=='linux'", {
39
+ "libraries": ["-lpcsclite"],
40
+ "include_dirs": ["/usr/include/PCSC"],
41
+ "cflags_cc": ["-fexceptions"]
42
+ }]
43
+ ]
44
+ }]
45
+ }