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 +1 -1
- package/README.md +453 -0
- package/binding.gyp +45 -0
- package/lib/devices.js +283 -0
- package/lib/errors.js +77 -0
- package/lib/index.d.ts +247 -0
- package/lib/index.js +75 -14
- package/package.json +56 -39
- package/src/addon.cpp +54 -0
- package/src/async_workers.cpp +234 -0
- package/src/async_workers.h +100 -0
- package/src/pcsc_card.cpp +248 -0
- package/src/pcsc_card.h +41 -0
- package/src/pcsc_context.cpp +224 -0
- package/src/pcsc_context.h +32 -0
- package/src/pcsc_errors.h +49 -0
- package/src/pcsc_reader.cpp +89 -0
- package/src/pcsc_reader.h +39 -0
- package/src/platform/pcsc.h +36 -0
- package/src/reader_monitor.cpp +352 -0
- package/src/reader_monitor.h +57 -0
- package/.prettierrc +0 -3
- package/README.MD +0 -371
- package/babel.config.json +0 -3
- package/demo/device-activated-promise.js +0 -12
- package/demo/device-activated.js +0 -12
- package/demo/device-deactivated-promise.js +0 -12
- package/demo/device-deactivated.js +0 -12
- package/demo/smartcard-demo.js +0 -100
- package/lib/Card.js +0 -129
- package/lib/CommandApdu.js +0 -109
- package/lib/Device.js +0 -138
- package/lib/Devices.js +0 -134
- package/lib/Iso7816Application.js +0 -169
- package/lib/ResponseApdu.js +0 -129
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include "platform/pcsc.h"
|
|
7
|
+
|
|
8
|
+
class PCSCReader : public Napi::ObjectWrap<PCSCReader> {
|
|
9
|
+
public:
|
|
10
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
11
|
+
static Napi::Object NewInstance(Napi::Env env, SCARDCONTEXT context,
|
|
12
|
+
const std::string& name, DWORD state,
|
|
13
|
+
const std::vector<uint8_t>& atr);
|
|
14
|
+
|
|
15
|
+
PCSCReader(const Napi::CallbackInfo& info);
|
|
16
|
+
|
|
17
|
+
// Accessors
|
|
18
|
+
const std::string& GetName() const { return name_; }
|
|
19
|
+
DWORD GetState() const { return state_; }
|
|
20
|
+
const std::vector<uint8_t>& GetAtr() const { return atr_; }
|
|
21
|
+
SCARDCONTEXT GetContext() const { return context_; }
|
|
22
|
+
|
|
23
|
+
// Update state (called from monitoring)
|
|
24
|
+
void UpdateState(DWORD state, const std::vector<uint8_t>& atr);
|
|
25
|
+
|
|
26
|
+
private:
|
|
27
|
+
static Napi::FunctionReference constructor;
|
|
28
|
+
|
|
29
|
+
std::string name_;
|
|
30
|
+
SCARDCONTEXT context_;
|
|
31
|
+
DWORD state_;
|
|
32
|
+
std::vector<uint8_t> atr_;
|
|
33
|
+
|
|
34
|
+
// JavaScript-exposed methods
|
|
35
|
+
Napi::Value GetNameValue(const Napi::CallbackInfo& info);
|
|
36
|
+
Napi::Value GetStateValue(const Napi::CallbackInfo& info);
|
|
37
|
+
Napi::Value GetAtrValue(const Napi::CallbackInfo& info);
|
|
38
|
+
Napi::Value Connect(const Napi::CallbackInfo& info);
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Cross-platform PC/SC header
|
|
4
|
+
// Normalizes differences between Windows, macOS, and Linux PC/SC libraries
|
|
5
|
+
|
|
6
|
+
#ifdef _WIN32
|
|
7
|
+
#include <winscard.h>
|
|
8
|
+
#pragma comment(lib, "winscard.lib")
|
|
9
|
+
#elif __APPLE__
|
|
10
|
+
#include <PCSC/winscard.h>
|
|
11
|
+
#include <PCSC/wintypes.h>
|
|
12
|
+
#else
|
|
13
|
+
// Linux
|
|
14
|
+
#include <winscard.h>
|
|
15
|
+
#include <reader.h>
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
// Normalize error codes that may be missing on some platforms
|
|
19
|
+
#ifndef SCARD_E_NO_READERS_AVAILABLE
|
|
20
|
+
#define SCARD_E_NO_READERS_AVAILABLE 0x8010002E
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
#ifndef SCARD_E_NO_SERVICE
|
|
24
|
+
#define SCARD_E_NO_SERVICE 0x8010001D
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
// Windows uses LPTSTR which can be wide, macOS/Linux use char*
|
|
28
|
+
// This ensures we work with char* consistently
|
|
29
|
+
#ifdef _WIN32
|
|
30
|
+
#ifndef SCARD_AUTOALLOCATE
|
|
31
|
+
#define SCARD_AUTOALLOCATE ((DWORD)-1)
|
|
32
|
+
#endif
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
// Helper macro for unused parameters
|
|
36
|
+
#define PCSC_UNUSED(x) (void)(x)
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#include "reader_monitor.h"
|
|
2
|
+
#include "pcsc_errors.h"
|
|
3
|
+
#include <cstring>
|
|
4
|
+
|
|
5
|
+
Napi::FunctionReference ReaderMonitor::constructor;
|
|
6
|
+
|
|
7
|
+
// Event data passed from worker thread to JS thread
|
|
8
|
+
struct EventData {
|
|
9
|
+
std::string eventType;
|
|
10
|
+
std::string readerName;
|
|
11
|
+
DWORD state;
|
|
12
|
+
std::vector<uint8_t> atr;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
Napi::Object ReaderMonitor::Init(Napi::Env env, Napi::Object exports) {
|
|
16
|
+
Napi::Function func = DefineClass(env, "ReaderMonitor", {
|
|
17
|
+
InstanceMethod("start", &ReaderMonitor::Start),
|
|
18
|
+
InstanceMethod("stop", &ReaderMonitor::Stop),
|
|
19
|
+
InstanceAccessor("isRunning", &ReaderMonitor::GetIsRunning, nullptr),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
constructor = Napi::Persistent(func);
|
|
23
|
+
constructor.SuppressDestruct();
|
|
24
|
+
|
|
25
|
+
exports.Set("ReaderMonitor", func);
|
|
26
|
+
return exports;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ReaderMonitor::ReaderMonitor(const Napi::CallbackInfo& info)
|
|
30
|
+
: Napi::ObjectWrap<ReaderMonitor>(info),
|
|
31
|
+
context_(0),
|
|
32
|
+
contextValid_(false),
|
|
33
|
+
running_(false) {
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ReaderMonitor::~ReaderMonitor() {
|
|
37
|
+
// Ensure monitoring is stopped
|
|
38
|
+
if (running_) {
|
|
39
|
+
running_ = false;
|
|
40
|
+
if (contextValid_) {
|
|
41
|
+
SCardCancel(context_);
|
|
42
|
+
}
|
|
43
|
+
if (monitorThread_.joinable()) {
|
|
44
|
+
monitorThread_.join();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (contextValid_) {
|
|
49
|
+
SCardReleaseContext(context_);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Napi::Value ReaderMonitor::Start(const Napi::CallbackInfo& info) {
|
|
54
|
+
Napi::Env env = info.Env();
|
|
55
|
+
|
|
56
|
+
if (running_) {
|
|
57
|
+
Napi::Error::New(env, "Monitor is already running").ThrowAsJavaScriptException();
|
|
58
|
+
return env.Undefined();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Require callback function
|
|
62
|
+
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
63
|
+
Napi::TypeError::New(env, "Callback function required").ThrowAsJavaScriptException();
|
|
64
|
+
return env.Undefined();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
68
|
+
|
|
69
|
+
// Establish PC/SC context
|
|
70
|
+
LONG result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context_);
|
|
71
|
+
if (result != SCARD_S_SUCCESS) {
|
|
72
|
+
Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
|
|
73
|
+
return env.Undefined();
|
|
74
|
+
}
|
|
75
|
+
contextValid_ = true;
|
|
76
|
+
|
|
77
|
+
// Create thread-safe function
|
|
78
|
+
tsfn_ = Napi::ThreadSafeFunction::New(
|
|
79
|
+
env,
|
|
80
|
+
callback,
|
|
81
|
+
"ReaderMonitor",
|
|
82
|
+
0, // Unlimited queue size
|
|
83
|
+
1, // 1 initial thread
|
|
84
|
+
[this](Napi::Env) {
|
|
85
|
+
// Invoke destructor callback - called when tsfn is released
|
|
86
|
+
running_ = false;
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Start monitoring
|
|
91
|
+
running_ = true;
|
|
92
|
+
monitorThread_ = std::thread(&ReaderMonitor::MonitorLoop, this);
|
|
93
|
+
|
|
94
|
+
return env.Undefined();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Napi::Value ReaderMonitor::Stop(const Napi::CallbackInfo& info) {
|
|
98
|
+
Napi::Env env = info.Env();
|
|
99
|
+
|
|
100
|
+
if (!running_) {
|
|
101
|
+
return env.Undefined();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
running_ = false;
|
|
105
|
+
|
|
106
|
+
// Cancel any blocking SCardGetStatusChange call
|
|
107
|
+
if (contextValid_) {
|
|
108
|
+
SCardCancel(context_);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Wait for thread to finish
|
|
112
|
+
if (monitorThread_.joinable()) {
|
|
113
|
+
monitorThread_.join();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Release thread-safe function
|
|
117
|
+
tsfn_.Release();
|
|
118
|
+
|
|
119
|
+
// Release context
|
|
120
|
+
if (contextValid_) {
|
|
121
|
+
SCardReleaseContext(context_);
|
|
122
|
+
contextValid_ = false;
|
|
123
|
+
context_ = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
readers_.clear();
|
|
127
|
+
|
|
128
|
+
return env.Undefined();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Napi::Value ReaderMonitor::GetIsRunning(const Napi::CallbackInfo& info) {
|
|
132
|
+
return Napi::Boolean::New(info.Env(), running_.load());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
void ReaderMonitor::MonitorLoop() {
|
|
136
|
+
// Get initial reader list
|
|
137
|
+
UpdateReaderList();
|
|
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
|
+
|
|
147
|
+
// Build initial states array with PnP notification
|
|
148
|
+
std::vector<SCARD_READERSTATE> states;
|
|
149
|
+
std::vector<std::string> readerNames;
|
|
150
|
+
|
|
151
|
+
while (running_) {
|
|
152
|
+
// Build states array
|
|
153
|
+
{
|
|
154
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
155
|
+
states.clear();
|
|
156
|
+
readerNames.clear();
|
|
157
|
+
|
|
158
|
+
// Add known readers
|
|
159
|
+
for (auto& reader : readers_) {
|
|
160
|
+
SCARD_READERSTATE state = {};
|
|
161
|
+
readerNames.push_back(reader.name);
|
|
162
|
+
state.szReader = readerNames.back().c_str();
|
|
163
|
+
state.dwCurrentState = reader.lastState;
|
|
164
|
+
states.push_back(state);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add PnP notification for new reader detection
|
|
168
|
+
readerNames.push_back("\\\\?PnP?\\Notification");
|
|
169
|
+
SCARD_READERSTATE pnpState = {};
|
|
170
|
+
pnpState.szReader = readerNames.back().c_str();
|
|
171
|
+
pnpState.dwCurrentState = SCARD_STATE_UNAWARE;
|
|
172
|
+
states.push_back(pnpState);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Wait for changes (with 1 second timeout for periodic refresh)
|
|
176
|
+
LONG result = SCardGetStatusChange(context_, 1000, states.data(), states.size());
|
|
177
|
+
|
|
178
|
+
if (!running_) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (result == SCARD_E_CANCELLED) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (result == SCARD_E_TIMEOUT) {
|
|
187
|
+
// No changes, continue
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (result != SCARD_S_SUCCESS) {
|
|
192
|
+
// Error - emit and continue
|
|
193
|
+
EmitEvent("error", GetPCSCErrorString(result), 0, {});
|
|
194
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Process changes
|
|
199
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
200
|
+
|
|
201
|
+
for (size_t i = 0; i < states.size(); i++) {
|
|
202
|
+
if (!(states[i].dwEventState & SCARD_STATE_CHANGED)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// PnP notification - reader list changed
|
|
207
|
+
if (readerNames[i] == "\\\\?PnP?\\Notification") {
|
|
208
|
+
// Get old reader names
|
|
209
|
+
std::vector<std::string> oldNames;
|
|
210
|
+
for (const auto& r : readers_) {
|
|
211
|
+
oldNames.push_back(r.name);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Update reader list
|
|
215
|
+
UpdateReaderList();
|
|
216
|
+
|
|
217
|
+
// Find new readers
|
|
218
|
+
for (const auto& r : readers_) {
|
|
219
|
+
bool found = false;
|
|
220
|
+
for (const auto& old : oldNames) {
|
|
221
|
+
if (old == r.name) {
|
|
222
|
+
found = true;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!found) {
|
|
227
|
+
EmitEvent("reader-attached", r.name, r.lastState, r.atr);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Find removed readers
|
|
232
|
+
for (const auto& old : oldNames) {
|
|
233
|
+
bool found = false;
|
|
234
|
+
for (const auto& r : readers_) {
|
|
235
|
+
if (r.name == old) {
|
|
236
|
+
found = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!found) {
|
|
241
|
+
EmitEvent("reader-detached", old, 0, {});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Reader state change
|
|
248
|
+
if (i < readers_.size()) {
|
|
249
|
+
DWORD oldState = readers_[i].lastState;
|
|
250
|
+
DWORD newState = states[i].dwEventState;
|
|
251
|
+
|
|
252
|
+
bool wasPresent = (oldState & SCARD_STATE_PRESENT) != 0;
|
|
253
|
+
bool isPresent = (newState & SCARD_STATE_PRESENT) != 0;
|
|
254
|
+
|
|
255
|
+
// Get ATR
|
|
256
|
+
std::vector<uint8_t> atr;
|
|
257
|
+
if (states[i].cbAtr > 0) {
|
|
258
|
+
atr.assign(states[i].rgbAtr, states[i].rgbAtr + states[i].cbAtr);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Update stored state
|
|
262
|
+
readers_[i].lastState = newState & ~SCARD_STATE_CHANGED;
|
|
263
|
+
readers_[i].atr = atr;
|
|
264
|
+
|
|
265
|
+
// Emit appropriate event
|
|
266
|
+
if (!wasPresent && isPresent) {
|
|
267
|
+
EmitEvent("card-inserted", readerNames[i], newState, atr);
|
|
268
|
+
} else if (wasPresent && !isPresent) {
|
|
269
|
+
EmitEvent("card-removed", readerNames[i], newState, {});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
void ReaderMonitor::UpdateReaderList() {
|
|
277
|
+
// Get reader list size
|
|
278
|
+
DWORD readersLen = 0;
|
|
279
|
+
LONG result = SCardListReaders(context_, nullptr, nullptr, &readersLen);
|
|
280
|
+
|
|
281
|
+
if (result == SCARD_E_NO_READERS_AVAILABLE || readersLen == 0) {
|
|
282
|
+
readers_.clear();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (result != SCARD_S_SUCCESS) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Get reader names
|
|
291
|
+
std::vector<char> buffer(readersLen);
|
|
292
|
+
result = SCardListReaders(context_, nullptr, buffer.data(), &readersLen);
|
|
293
|
+
|
|
294
|
+
if (result != SCARD_S_SUCCESS) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Parse multi-string
|
|
299
|
+
std::vector<std::string> newNames;
|
|
300
|
+
const char* p = buffer.data();
|
|
301
|
+
while (*p != '\0') {
|
|
302
|
+
newNames.push_back(std::string(p));
|
|
303
|
+
p += strlen(p) + 1;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Get initial state for new readers
|
|
307
|
+
std::vector<SCARD_READERSTATE> states(newNames.size());
|
|
308
|
+
for (size_t i = 0; i < newNames.size(); i++) {
|
|
309
|
+
states[i].szReader = newNames[i].c_str();
|
|
310
|
+
states[i].dwCurrentState = SCARD_STATE_UNAWARE;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
SCardGetStatusChange(context_, 0, states.data(), states.size());
|
|
314
|
+
|
|
315
|
+
// Update reader list
|
|
316
|
+
readers_.clear();
|
|
317
|
+
for (size_t i = 0; i < newNames.size(); i++) {
|
|
318
|
+
ReaderInfo info;
|
|
319
|
+
info.name = newNames[i];
|
|
320
|
+
info.lastState = states[i].dwEventState & ~SCARD_STATE_CHANGED;
|
|
321
|
+
if (states[i].cbAtr > 0) {
|
|
322
|
+
info.atr.assign(states[i].rgbAtr, states[i].rgbAtr + states[i].cbAtr);
|
|
323
|
+
}
|
|
324
|
+
readers_.push_back(info);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
void ReaderMonitor::EmitEvent(const std::string& eventType, const std::string& readerName,
|
|
329
|
+
DWORD state, const std::vector<uint8_t>& atr) {
|
|
330
|
+
// Copy data for transfer to JS thread
|
|
331
|
+
EventData* data = new EventData{eventType, readerName, state, atr};
|
|
332
|
+
|
|
333
|
+
// Call JavaScript callback on main thread
|
|
334
|
+
tsfn_.BlockingCall(data, [](Napi::Env env, Napi::Function callback, EventData* data) {
|
|
335
|
+
// Build event object
|
|
336
|
+
Napi::Object event = Napi::Object::New(env);
|
|
337
|
+
event.Set("type", Napi::String::New(env, data->eventType));
|
|
338
|
+
event.Set("reader", Napi::String::New(env, data->readerName));
|
|
339
|
+
event.Set("state", Napi::Number::New(env, data->state));
|
|
340
|
+
|
|
341
|
+
if (!data->atr.empty()) {
|
|
342
|
+
event.Set("atr", Napi::Buffer<uint8_t>::Copy(env, data->atr.data(), data->atr.size()));
|
|
343
|
+
} else {
|
|
344
|
+
event.Set("atr", env.Null());
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Call the callback
|
|
348
|
+
callback.Call({event});
|
|
349
|
+
|
|
350
|
+
delete data;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <thread>
|
|
5
|
+
#include <atomic>
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <mutex>
|
|
9
|
+
#include "platform/pcsc.h"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ReaderMonitor - Native PC/SC event monitoring using ThreadSafeFunction
|
|
13
|
+
*
|
|
14
|
+
* Runs a background thread that monitors for reader/card state changes
|
|
15
|
+
* and emits events to JavaScript without blocking the main thread.
|
|
16
|
+
*/
|
|
17
|
+
class ReaderMonitor : public Napi::ObjectWrap<ReaderMonitor> {
|
|
18
|
+
public:
|
|
19
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
20
|
+
|
|
21
|
+
ReaderMonitor(const Napi::CallbackInfo& info);
|
|
22
|
+
~ReaderMonitor();
|
|
23
|
+
|
|
24
|
+
private:
|
|
25
|
+
static Napi::FunctionReference constructor;
|
|
26
|
+
|
|
27
|
+
// PC/SC context
|
|
28
|
+
SCARDCONTEXT context_;
|
|
29
|
+
bool contextValid_;
|
|
30
|
+
|
|
31
|
+
// Monitoring thread
|
|
32
|
+
std::thread monitorThread_;
|
|
33
|
+
std::atomic<bool> running_;
|
|
34
|
+
std::mutex mutex_;
|
|
35
|
+
|
|
36
|
+
// Thread-safe function for emitting events
|
|
37
|
+
Napi::ThreadSafeFunction tsfn_;
|
|
38
|
+
|
|
39
|
+
// Current known reader states
|
|
40
|
+
struct ReaderInfo {
|
|
41
|
+
std::string name;
|
|
42
|
+
DWORD lastState;
|
|
43
|
+
std::vector<uint8_t> atr;
|
|
44
|
+
};
|
|
45
|
+
std::vector<ReaderInfo> readers_;
|
|
46
|
+
|
|
47
|
+
// JavaScript methods
|
|
48
|
+
Napi::Value Start(const Napi::CallbackInfo& info);
|
|
49
|
+
Napi::Value Stop(const Napi::CallbackInfo& info);
|
|
50
|
+
Napi::Value GetIsRunning(const Napi::CallbackInfo& info);
|
|
51
|
+
|
|
52
|
+
// Internal methods
|
|
53
|
+
void MonitorLoop();
|
|
54
|
+
void UpdateReaderList();
|
|
55
|
+
void EmitEvent(const std::string& eventType, const std::string& readerName,
|
|
56
|
+
DWORD state, const std::vector<uint8_t>& atr);
|
|
57
|
+
};
|
package/.prettierrc
DELETED