smartcard 2.0.0 → 2.0.2

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/README.md CHANGED
@@ -1,42 +1,108 @@
1
1
  # smartcard
2
2
 
3
- Modern PC/SC (Personal Computer/Smart Card) bindings for Node.js using N-API.
3
+ ```
4
+ ╭────────────────────────────────────────╮
5
+ │ │
6
+ │ ╭───────────╮ │
7
+ │ │ ▄▄▄ ▄▄▄▄▄ │ │
8
+ │ │ ███ ▄▄▄▄▄ │ │
9
+ │ │ ▀▀▀ ▀▀▀▀▀ │ │
10
+ │ ╰───────────╯ │
11
+ │ │
12
+ │ ░░░░░░░░░░░░ │
13
+ │ ░░░░░░░░░░░░ │
14
+ ╰────────────────────────────────────────╯
15
+ ```
4
16
 
5
- Unlike older NAN-based bindings that break with each Node.js major version, this library uses N-API for ABI stability across Node.js versions 12, 14, 16, 18, 20, 22, 24, and beyond - without recompilation.
17
+ Stable PC/SC smart card bindings for Node.js.
6
18
 
7
- ## Features
19
+ Works with Node.js 12+ without recompilation. Built on N-API for long-term stability.
8
20
 
9
- - **ABI Stable**: Works across Node.js versions without recompilation
10
- - **Async/Promise-based**: Non-blocking card operations
11
- - **Event-driven API**: High-level `Devices` class with EventEmitter
12
- - **TypeScript support**: Full type definitions included
13
- - **Cross-platform**: Windows, macOS, and Linux
21
+ ## Getting Started
14
22
 
15
- ## Installation
23
+ ### 1. Install the package
16
24
 
17
25
  ```bash
18
26
  npm install smartcard
19
27
  ```
20
28
 
21
- ### Prerequisites
22
-
23
- **macOS**: No additional setup required (uses built-in PCSC.framework)
29
+ ### 2. Platform setup
24
30
 
25
- **Windows**: No additional setup required (uses built-in winscard.dll)
31
+ **macOS/Windows**: Ready to go - no additional setup needed.
26
32
 
27
33
  **Linux**:
28
34
  ```bash
29
- # Debian/Ubuntu
30
- sudo apt-get install libpcsclite-dev pcscd
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
31
38
 
32
- # Fedora/RHEL
33
- sudo dnf install pcsc-lite-devel pcsc-lite
34
-
35
- # Start the PC/SC daemon
39
+ # Start the daemon
36
40
  sudo systemctl start pcscd
37
41
  ```
38
42
 
39
- ## Quick Start
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
40
106
 
41
107
  ### High-Level API (Event-Driven)
42
108
 
package/lib/devices.js CHANGED
@@ -1,14 +1,29 @@
1
+ // @ts-check
1
2
  'use strict';
2
3
 
3
4
  const EventEmitter = require('events');
4
5
  const addon = require('../build/Release/smartcard_napi.node');
5
6
 
7
+ /**
8
+ * @typedef {import('./index').Reader} Reader
9
+ * @typedef {import('./index').Card} Card
10
+ * @typedef {import('./index').MonitorEvent} MonitorEvent
11
+ * @typedef {import('./index').Context} ContextType
12
+ * @typedef {import('./index').ReaderMonitor} ReaderMonitorType
13
+ */
14
+
6
15
  const { Context, ReaderMonitor } = addon;
7
16
  const SCARD_STATE_PRESENT = addon.SCARD_STATE_PRESENT;
8
17
  const SCARD_SHARE_SHARED = addon.SCARD_SHARE_SHARED;
9
18
  const SCARD_PROTOCOL_T0 = addon.SCARD_PROTOCOL_T0;
10
19
  const SCARD_PROTOCOL_T1 = addon.SCARD_PROTOCOL_T1;
11
20
 
21
+ /**
22
+ * @typedef {Object} ReaderState
23
+ * @property {boolean} hasCard
24
+ * @property {Card|null} card
25
+ */
26
+
12
27
  /**
13
28
  * High-level event-driven API for PC/SC devices
14
29
  *
@@ -21,14 +36,22 @@ const SCARD_PROTOCOL_T1 = addon.SCARD_PROTOCOL_T1;
21
36
  * - 'card-inserted': Emitted when a card is inserted
22
37
  * - 'card-removed': Emitted when a card is removed
23
38
  * - 'error': Emitted on errors
39
+ *
40
+ * @extends EventEmitter
24
41
  */
25
42
  class Devices extends EventEmitter {
26
43
  constructor() {
27
44
  super();
45
+ /** @type {ReaderMonitorType|null} */
28
46
  this._monitor = null;
47
+ /** @type {ContextType|null} */
29
48
  this._context = null;
49
+ /** @type {boolean} */
30
50
  this._running = false;
31
- this._readers = new Map(); // name -> { hasCard, card }
51
+ /** @type {Map<string, ReaderState>} */
52
+ this._readers = new Map();
53
+ /** @type {Promise<void>} Event queue to serialize event handling */
54
+ this._eventQueue = Promise.resolve();
32
55
  }
33
56
 
34
57
  /**
@@ -95,7 +118,7 @@ class Devices extends EventEmitter {
95
118
 
96
119
  /**
97
120
  * List currently known readers
98
- * @returns {Array} Array of reader names
121
+ * @returns {Reader[]} Array of readers
99
122
  */
100
123
  listReaders() {
101
124
  if (!this._context || !this._context.isValid) {
@@ -110,8 +133,20 @@ class Devices extends EventEmitter {
110
133
 
111
134
  /**
112
135
  * Handle events from native monitor
136
+ * Queues events to prevent race conditions when multiple events arrive concurrently
137
+ * @param {MonitorEvent} event
138
+ */
139
+ _handleEvent(event) {
140
+ // Chain this event onto the queue to serialize processing
141
+ this._eventQueue = this._eventQueue.then(() => this._processEvent(event));
142
+ }
143
+
144
+ /**
145
+ * Process a single event (called sequentially via queue)
146
+ * @param {MonitorEvent} event
147
+ * @returns {Promise<void>}
113
148
  */
114
- async _handleEvent(event) {
149
+ async _processEvent(event) {
115
150
  if (!this._running) {
116
151
  return;
117
152
  }
@@ -120,7 +155,7 @@ class Devices extends EventEmitter {
120
155
 
121
156
  switch (type) {
122
157
  case 'reader-attached':
123
- this._handleReaderAttached(readerName, state, atr);
158
+ await this._handleReaderAttached(readerName, state, atr);
124
159
  break;
125
160
 
126
161
  case 'reader-detached':
@@ -144,8 +179,12 @@ class Devices extends EventEmitter {
144
179
 
145
180
  /**
146
181
  * Handle reader attached
182
+ * @param {string} readerName
183
+ * @param {number} state
184
+ * @param {Buffer|null} atr
185
+ * @returns {Promise<void>}
147
186
  */
148
- _handleReaderAttached(readerName, state, atr) {
187
+ async _handleReaderAttached(readerName, state, atr) {
149
188
  // Initialize reader state
150
189
  this._readers.set(readerName, {
151
190
  hasCard: false,
@@ -163,12 +202,13 @@ class Devices extends EventEmitter {
163
202
 
164
203
  // Check if card is already present
165
204
  if ((state & SCARD_STATE_PRESENT) !== 0) {
166
- this._handleCardInserted(readerName, state, atr);
205
+ await this._handleCardInserted(readerName, state, atr);
167
206
  }
168
207
  }
169
208
 
170
209
  /**
171
210
  * Handle reader detached
211
+ * @param {string} readerName
172
212
  */
173
213
  _handleReaderDetached(readerName) {
174
214
  const state = this._readers.get(readerName);
@@ -186,6 +226,10 @@ class Devices extends EventEmitter {
186
226
 
187
227
  /**
188
228
  * Handle card inserted
229
+ * @param {string} readerName
230
+ * @param {number} eventState
231
+ * @param {Buffer|null} atr
232
+ * @returns {Promise<void>}
189
233
  */
190
234
  async _handleCardInserted(readerName, eventState, atr) {
191
235
  let state = this._readers.get(readerName);
@@ -222,6 +266,7 @@ class Devices extends EventEmitter {
222
266
 
223
267
  /**
224
268
  * Handle card removed
269
+ * @param {string} readerName
225
270
  */
226
271
  _handleCardRemoved(readerName) {
227
272
  const state = this._readers.get(readerName);
package/lib/errors.js CHANGED
@@ -1,9 +1,14 @@
1
+ // @ts-check
1
2
  'use strict';
2
3
 
3
4
  /**
4
5
  * Base error class for PC/SC errors
5
6
  */
6
7
  class PCSCError extends Error {
8
+ /**
9
+ * @param {string} message
10
+ * @param {number} code
11
+ */
7
12
  constructor(message, code) {
8
13
  super(message);
9
14
  this.name = 'PCSCError';
package/lib/index.js CHANGED
@@ -1,7 +1,6 @@
1
+ // @ts-check
1
2
  'use strict';
2
3
 
3
- const path = require('path');
4
-
5
4
  // Load native addon
6
5
  const addon = require('../build/Release/smartcard_napi.node');
7
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartcard",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "PC/SC bindings for Node.js using N-API - ABI stable across Node.js versions",
5
5
  "author": "Tom KP",
6
6
  "license": "MIT",
@@ -136,6 +136,14 @@ void ReaderMonitor::MonitorLoop() {
136
136
  // Get initial reader list
137
137
  UpdateReaderList();
138
138
 
139
+ // Emit reader-attached events for all pre-existing readers (Issue #30)
140
+ {
141
+ std::lock_guard<std::mutex> lock(mutex_);
142
+ for (const auto& reader : readers_) {
143
+ EmitEvent("reader-attached", reader.name, reader.lastState, reader.atr);
144
+ }
145
+ }
146
+
139
147
  // Build initial states array with PnP notification
140
148
  std::vector<SCARD_READERSTATE> states;
141
149
  std::vector<std::string> readerNames;