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.
@@ -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
@@ -1,3 +0,0 @@
1
- {
2
- "singleQuote": true
3
- }