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 +86 -20
- package/lib/devices.js +51 -6
- package/lib/errors.js +5 -0
- package/lib/index.js +1 -2
- package/package.json +1 -1
- package/src/reader_monitor.cpp +8 -0
package/README.md
CHANGED
|
@@ -1,42 +1,108 @@
|
|
|
1
1
|
# smartcard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```
|
|
4
|
+
╭────────────────────────────────────────╮
|
|
5
|
+
│ │
|
|
6
|
+
│ ╭───────────╮ │
|
|
7
|
+
│ │ ▄▄▄ ▄▄▄▄▄ │ │
|
|
8
|
+
│ │ ███ ▄▄▄▄▄ │ │
|
|
9
|
+
│ │ ▀▀▀ ▀▀▀▀▀ │ │
|
|
10
|
+
│ ╰───────────╯ │
|
|
11
|
+
│ │
|
|
12
|
+
│ ░░░░░░░░░░░░ │
|
|
13
|
+
│ ░░░░░░░░░░░░ │
|
|
14
|
+
╰────────────────────────────────────────╯
|
|
15
|
+
```
|
|
4
16
|
|
|
5
|
-
|
|
17
|
+
Stable PC/SC smart card bindings for Node.js.
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
Works with Node.js 12+ without recompilation. Built on N-API for long-term stability.
|
|
8
20
|
|
|
9
|
-
|
|
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
|
-
|
|
23
|
+
### 1. Install the package
|
|
16
24
|
|
|
17
25
|
```bash
|
|
18
26
|
npm install smartcard
|
|
19
27
|
```
|
|
20
28
|
|
|
21
|
-
###
|
|
22
|
-
|
|
23
|
-
**macOS**: No additional setup required (uses built-in PCSC.framework)
|
|
29
|
+
### 2. Platform setup
|
|
24
30
|
|
|
25
|
-
**Windows**:
|
|
31
|
+
**macOS/Windows**: Ready to go - no additional setup needed.
|
|
26
32
|
|
|
27
33
|
**Linux**:
|
|
28
34
|
```bash
|
|
29
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
package/package.json
CHANGED
package/src/reader_monitor.cpp
CHANGED
|
@@ -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;
|