smartcard 1.0.46 → 2.0.0

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.
@@ -0,0 +1,344 @@
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
+ // Build initial states array with PnP notification
140
+ std::vector<SCARD_READERSTATE> states;
141
+ std::vector<std::string> readerNames;
142
+
143
+ while (running_) {
144
+ // Build states array
145
+ {
146
+ std::lock_guard<std::mutex> lock(mutex_);
147
+ states.clear();
148
+ readerNames.clear();
149
+
150
+ // Add known readers
151
+ for (auto& reader : readers_) {
152
+ SCARD_READERSTATE state = {};
153
+ readerNames.push_back(reader.name);
154
+ state.szReader = readerNames.back().c_str();
155
+ state.dwCurrentState = reader.lastState;
156
+ states.push_back(state);
157
+ }
158
+
159
+ // Add PnP notification for new reader detection
160
+ readerNames.push_back("\\\\?PnP?\\Notification");
161
+ SCARD_READERSTATE pnpState = {};
162
+ pnpState.szReader = readerNames.back().c_str();
163
+ pnpState.dwCurrentState = SCARD_STATE_UNAWARE;
164
+ states.push_back(pnpState);
165
+ }
166
+
167
+ // Wait for changes (with 1 second timeout for periodic refresh)
168
+ LONG result = SCardGetStatusChange(context_, 1000, states.data(), states.size());
169
+
170
+ if (!running_) {
171
+ break;
172
+ }
173
+
174
+ if (result == SCARD_E_CANCELLED) {
175
+ break;
176
+ }
177
+
178
+ if (result == SCARD_E_TIMEOUT) {
179
+ // No changes, continue
180
+ continue;
181
+ }
182
+
183
+ if (result != SCARD_S_SUCCESS) {
184
+ // Error - emit and continue
185
+ EmitEvent("error", GetPCSCErrorString(result), 0, {});
186
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
187
+ continue;
188
+ }
189
+
190
+ // Process changes
191
+ std::lock_guard<std::mutex> lock(mutex_);
192
+
193
+ for (size_t i = 0; i < states.size(); i++) {
194
+ if (!(states[i].dwEventState & SCARD_STATE_CHANGED)) {
195
+ continue;
196
+ }
197
+
198
+ // PnP notification - reader list changed
199
+ if (readerNames[i] == "\\\\?PnP?\\Notification") {
200
+ // Get old reader names
201
+ std::vector<std::string> oldNames;
202
+ for (const auto& r : readers_) {
203
+ oldNames.push_back(r.name);
204
+ }
205
+
206
+ // Update reader list
207
+ UpdateReaderList();
208
+
209
+ // Find new readers
210
+ for (const auto& r : readers_) {
211
+ bool found = false;
212
+ for (const auto& old : oldNames) {
213
+ if (old == r.name) {
214
+ found = true;
215
+ break;
216
+ }
217
+ }
218
+ if (!found) {
219
+ EmitEvent("reader-attached", r.name, r.lastState, r.atr);
220
+ }
221
+ }
222
+
223
+ // Find removed readers
224
+ for (const auto& old : oldNames) {
225
+ bool found = false;
226
+ for (const auto& r : readers_) {
227
+ if (r.name == old) {
228
+ found = true;
229
+ break;
230
+ }
231
+ }
232
+ if (!found) {
233
+ EmitEvent("reader-detached", old, 0, {});
234
+ }
235
+ }
236
+ continue;
237
+ }
238
+
239
+ // Reader state change
240
+ if (i < readers_.size()) {
241
+ DWORD oldState = readers_[i].lastState;
242
+ DWORD newState = states[i].dwEventState;
243
+
244
+ bool wasPresent = (oldState & SCARD_STATE_PRESENT) != 0;
245
+ bool isPresent = (newState & SCARD_STATE_PRESENT) != 0;
246
+
247
+ // Get ATR
248
+ std::vector<uint8_t> atr;
249
+ if (states[i].cbAtr > 0) {
250
+ atr.assign(states[i].rgbAtr, states[i].rgbAtr + states[i].cbAtr);
251
+ }
252
+
253
+ // Update stored state
254
+ readers_[i].lastState = newState & ~SCARD_STATE_CHANGED;
255
+ readers_[i].atr = atr;
256
+
257
+ // Emit appropriate event
258
+ if (!wasPresent && isPresent) {
259
+ EmitEvent("card-inserted", readerNames[i], newState, atr);
260
+ } else if (wasPresent && !isPresent) {
261
+ EmitEvent("card-removed", readerNames[i], newState, {});
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ void ReaderMonitor::UpdateReaderList() {
269
+ // Get reader list size
270
+ DWORD readersLen = 0;
271
+ LONG result = SCardListReaders(context_, nullptr, nullptr, &readersLen);
272
+
273
+ if (result == SCARD_E_NO_READERS_AVAILABLE || readersLen == 0) {
274
+ readers_.clear();
275
+ return;
276
+ }
277
+
278
+ if (result != SCARD_S_SUCCESS) {
279
+ return;
280
+ }
281
+
282
+ // Get reader names
283
+ std::vector<char> buffer(readersLen);
284
+ result = SCardListReaders(context_, nullptr, buffer.data(), &readersLen);
285
+
286
+ if (result != SCARD_S_SUCCESS) {
287
+ return;
288
+ }
289
+
290
+ // Parse multi-string
291
+ std::vector<std::string> newNames;
292
+ const char* p = buffer.data();
293
+ while (*p != '\0') {
294
+ newNames.push_back(std::string(p));
295
+ p += strlen(p) + 1;
296
+ }
297
+
298
+ // Get initial state for new readers
299
+ std::vector<SCARD_READERSTATE> states(newNames.size());
300
+ for (size_t i = 0; i < newNames.size(); i++) {
301
+ states[i].szReader = newNames[i].c_str();
302
+ states[i].dwCurrentState = SCARD_STATE_UNAWARE;
303
+ }
304
+
305
+ SCardGetStatusChange(context_, 0, states.data(), states.size());
306
+
307
+ // Update reader list
308
+ readers_.clear();
309
+ for (size_t i = 0; i < newNames.size(); i++) {
310
+ ReaderInfo info;
311
+ info.name = newNames[i];
312
+ info.lastState = states[i].dwEventState & ~SCARD_STATE_CHANGED;
313
+ if (states[i].cbAtr > 0) {
314
+ info.atr.assign(states[i].rgbAtr, states[i].rgbAtr + states[i].cbAtr);
315
+ }
316
+ readers_.push_back(info);
317
+ }
318
+ }
319
+
320
+ void ReaderMonitor::EmitEvent(const std::string& eventType, const std::string& readerName,
321
+ DWORD state, const std::vector<uint8_t>& atr) {
322
+ // Copy data for transfer to JS thread
323
+ EventData* data = new EventData{eventType, readerName, state, atr};
324
+
325
+ // Call JavaScript callback on main thread
326
+ tsfn_.BlockingCall(data, [](Napi::Env env, Napi::Function callback, EventData* data) {
327
+ // Build event object
328
+ Napi::Object event = Napi::Object::New(env);
329
+ event.Set("type", Napi::String::New(env, data->eventType));
330
+ event.Set("reader", Napi::String::New(env, data->readerName));
331
+ event.Set("state", Napi::Number::New(env, data->state));
332
+
333
+ if (!data->atr.empty()) {
334
+ event.Set("atr", Napi::Buffer<uint8_t>::Copy(env, data->atr.data(), data->atr.size()));
335
+ } else {
336
+ event.Set("atr", env.Null());
337
+ }
338
+
339
+ // Call the callback
340
+ callback.Call({event});
341
+
342
+ delete data;
343
+ });
344
+ }
@@ -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
@@ -1,3 +0,0 @@
1
- {
2
- "singleQuote": true
3
- }