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,224 @@
1
+ #include "pcsc_context.h"
2
+ #include "pcsc_reader.h"
3
+ #include "pcsc_errors.h"
4
+ #include "async_workers.h"
5
+ #include <cstring>
6
+
7
+ Napi::FunctionReference PCSCContext::constructor;
8
+
9
+ Napi::Object PCSCContext::Init(Napi::Env env, Napi::Object exports) {
10
+ Napi::Function func = DefineClass(env, "Context", {
11
+ InstanceMethod("listReaders", &PCSCContext::ListReaders),
12
+ InstanceMethod("waitForChange", &PCSCContext::WaitForChange),
13
+ InstanceMethod("cancel", &PCSCContext::Cancel),
14
+ InstanceMethod("close", &PCSCContext::Close),
15
+ InstanceAccessor("isValid", &PCSCContext::GetIsValid, nullptr),
16
+ });
17
+
18
+ constructor = Napi::Persistent(func);
19
+ constructor.SuppressDestruct();
20
+
21
+ exports.Set("Context", func);
22
+ return exports;
23
+ }
24
+
25
+ Napi::Object PCSCContext::NewInstance(Napi::Env env) {
26
+ return constructor.New({});
27
+ }
28
+
29
+ PCSCContext::PCSCContext(const Napi::CallbackInfo& info)
30
+ : Napi::ObjectWrap<PCSCContext>(info), context_(0), valid_(false) {
31
+
32
+ Napi::Env env = info.Env();
33
+
34
+ // Establish PC/SC context
35
+ LONG result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context_);
36
+
37
+ if (result != SCARD_S_SUCCESS) {
38
+ Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
39
+ return;
40
+ }
41
+
42
+ valid_ = true;
43
+ }
44
+
45
+ PCSCContext::~PCSCContext() {
46
+ if (valid_ && context_ != 0) {
47
+ SCardReleaseContext(context_);
48
+ valid_ = false;
49
+ context_ = 0;
50
+ }
51
+ }
52
+
53
+ Napi::Value PCSCContext::ListReaders(const Napi::CallbackInfo& info) {
54
+ Napi::Env env = info.Env();
55
+
56
+ if (!valid_) {
57
+ Napi::Error::New(env, "Context is not valid").ThrowAsJavaScriptException();
58
+ return env.Null();
59
+ }
60
+
61
+ // First call to get buffer size
62
+ DWORD readersLen = 0;
63
+ LONG result = SCardListReaders(context_, nullptr, nullptr, &readersLen);
64
+
65
+ if (result == SCARD_E_NO_READERS_AVAILABLE) {
66
+ // No readers - return empty array
67
+ return Napi::Array::New(env, 0);
68
+ }
69
+
70
+ if (result != SCARD_S_SUCCESS) {
71
+ Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
72
+ return env.Null();
73
+ }
74
+
75
+ // Allocate buffer and get reader names
76
+ std::vector<char> readersBuffer(readersLen);
77
+ result = SCardListReaders(context_, nullptr, readersBuffer.data(), &readersLen);
78
+
79
+ if (result != SCARD_S_SUCCESS) {
80
+ Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
81
+ return env.Null();
82
+ }
83
+
84
+ // Parse multi-string result (null-separated, double-null terminated)
85
+ std::vector<std::string> readerNames;
86
+ const char* p = readersBuffer.data();
87
+ while (*p != '\0') {
88
+ readerNames.push_back(std::string(p));
89
+ p += strlen(p) + 1;
90
+ }
91
+
92
+ // Get initial state for each reader
93
+ std::vector<SCARD_READERSTATE> states(readerNames.size());
94
+ for (size_t i = 0; i < readerNames.size(); i++) {
95
+ states[i].szReader = readerNames[i].c_str();
96
+ states[i].dwCurrentState = SCARD_STATE_UNAWARE;
97
+ }
98
+
99
+ // Get current state (non-blocking with 0 timeout)
100
+ result = SCardGetStatusChange(context_, 0, states.data(), states.size());
101
+
102
+ // Create Reader objects
103
+ Napi::Array readers = Napi::Array::New(env, readerNames.size());
104
+ for (size_t i = 0; i < readerNames.size(); i++) {
105
+ std::vector<uint8_t> atr;
106
+ if (result == SCARD_S_SUCCESS && states[i].cbAtr > 0) {
107
+ atr.assign(states[i].rgbAtr, states[i].rgbAtr + states[i].cbAtr);
108
+ }
109
+ DWORD state = (result == SCARD_S_SUCCESS) ? states[i].dwEventState : 0;
110
+
111
+ Napi::Object reader = PCSCReader::NewInstance(env, context_, readerNames[i], state, atr);
112
+ readers.Set(static_cast<uint32_t>(i), reader);
113
+ }
114
+
115
+ return readers;
116
+ }
117
+
118
+ Napi::Value PCSCContext::WaitForChange(const Napi::CallbackInfo& info) {
119
+ Napi::Env env = info.Env();
120
+
121
+ if (!valid_) {
122
+ Napi::Error::New(env, "Context is not valid").ThrowAsJavaScriptException();
123
+ return env.Null();
124
+ }
125
+
126
+ // Parse arguments: readers array (optional), timeout (optional)
127
+ std::vector<std::string> readerNames;
128
+ std::vector<DWORD> currentStates;
129
+ DWORD timeout = INFINITE;
130
+
131
+ size_t argIndex = 0;
132
+
133
+ // First argument can be array of readers or timeout
134
+ if (info.Length() > 0 && info[0].IsArray()) {
135
+ Napi::Array readersArray = info[0].As<Napi::Array>();
136
+ for (uint32_t i = 0; i < readersArray.Length(); i++) {
137
+ Napi::Value item = readersArray.Get(i);
138
+ if (item.IsObject()) {
139
+ // It's a Reader object
140
+ Napi::Object obj = item.As<Napi::Object>();
141
+ if (obj.Has("name")) {
142
+ readerNames.push_back(obj.Get("name").As<Napi::String>().Utf8Value());
143
+ if (obj.Has("state")) {
144
+ currentStates.push_back(obj.Get("state").As<Napi::Number>().Uint32Value());
145
+ } else {
146
+ currentStates.push_back(SCARD_STATE_UNAWARE);
147
+ }
148
+ }
149
+ } else if (item.IsString()) {
150
+ readerNames.push_back(item.As<Napi::String>().Utf8Value());
151
+ currentStates.push_back(SCARD_STATE_UNAWARE);
152
+ }
153
+ }
154
+ argIndex++;
155
+ }
156
+
157
+ // Next argument is timeout
158
+ if (info.Length() > argIndex && info[argIndex].IsNumber()) {
159
+ timeout = info[argIndex].As<Napi::Number>().Uint32Value();
160
+ }
161
+
162
+ // If no readers specified, get all readers
163
+ if (readerNames.empty()) {
164
+ DWORD readersLen = 0;
165
+ LONG result = SCardListReaders(context_, nullptr, nullptr, &readersLen);
166
+
167
+ if (result == SCARD_E_NO_READERS_AVAILABLE) {
168
+ // Add PnP notification to detect when readers are attached
169
+ readerNames.push_back("\\\\?PnP?\\Notification");
170
+ currentStates.push_back(SCARD_STATE_UNAWARE);
171
+ } else if (result == SCARD_S_SUCCESS) {
172
+ std::vector<char> readersBuffer(readersLen);
173
+ result = SCardListReaders(context_, nullptr, readersBuffer.data(), &readersLen);
174
+ if (result == SCARD_S_SUCCESS) {
175
+ const char* p = readersBuffer.data();
176
+ while (*p != '\0') {
177
+ readerNames.push_back(std::string(p));
178
+ currentStates.push_back(SCARD_STATE_UNAWARE);
179
+ p += strlen(p) + 1;
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ // Create promise and start async worker
186
+ Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
187
+
188
+ WaitForChangeWorker* worker = new WaitForChangeWorker(
189
+ env, context_, readerNames, currentStates, timeout, deferred);
190
+ worker->Queue();
191
+
192
+ return deferred.Promise();
193
+ }
194
+
195
+ Napi::Value PCSCContext::Cancel(const Napi::CallbackInfo& info) {
196
+ Napi::Env env = info.Env();
197
+
198
+ if (!valid_) {
199
+ return env.Undefined();
200
+ }
201
+
202
+ LONG result = SCardCancel(context_);
203
+ if (result != SCARD_S_SUCCESS && result != SCARD_E_INVALID_HANDLE) {
204
+ Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
205
+ }
206
+
207
+ return env.Undefined();
208
+ }
209
+
210
+ Napi::Value PCSCContext::Close(const Napi::CallbackInfo& info) {
211
+ Napi::Env env = info.Env();
212
+
213
+ if (valid_ && context_ != 0) {
214
+ SCardReleaseContext(context_);
215
+ valid_ = false;
216
+ context_ = 0;
217
+ }
218
+
219
+ return env.Undefined();
220
+ }
221
+
222
+ Napi::Value PCSCContext::GetIsValid(const Napi::CallbackInfo& info) {
223
+ return Napi::Boolean::New(info.Env(), valid_);
224
+ }
@@ -0,0 +1,32 @@
1
+ #pragma once
2
+
3
+ #include <napi.h>
4
+ #include <string>
5
+ #include <vector>
6
+ #include "platform/pcsc.h"
7
+
8
+ class PCSCContext : public Napi::ObjectWrap<PCSCContext> {
9
+ public:
10
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
11
+ static Napi::Object NewInstance(Napi::Env env);
12
+
13
+ PCSCContext(const Napi::CallbackInfo& info);
14
+ ~PCSCContext();
15
+
16
+ // Get the raw context handle (for use by other classes)
17
+ SCARDCONTEXT GetContext() const { return context_; }
18
+ bool IsValid() const { return valid_; }
19
+
20
+ private:
21
+ static Napi::FunctionReference constructor;
22
+
23
+ SCARDCONTEXT context_;
24
+ bool valid_;
25
+
26
+ // JavaScript-exposed methods
27
+ Napi::Value ListReaders(const Napi::CallbackInfo& info);
28
+ Napi::Value WaitForChange(const Napi::CallbackInfo& info);
29
+ Napi::Value Cancel(const Napi::CallbackInfo& info);
30
+ Napi::Value Close(const Napi::CallbackInfo& info);
31
+ Napi::Value GetIsValid(const Napi::CallbackInfo& info);
32
+ };
@@ -0,0 +1,49 @@
1
+ #pragma once
2
+
3
+ #include "platform/pcsc.h"
4
+
5
+ // Convert PC/SC error codes to human-readable strings
6
+ // Using explicit casts to handle unsigned->signed on macOS
7
+ inline const char* GetPCSCErrorString(LONG code) {
8
+ // Cast to unsigned for comparison to avoid sign issues
9
+ DWORD ucode = static_cast<DWORD>(code);
10
+
11
+ if (ucode == SCARD_S_SUCCESS) return "Success";
12
+ if (ucode == SCARD_E_CANCELLED) return "Operation cancelled";
13
+ if (ucode == SCARD_E_CANT_DISPOSE) return "Cannot dispose handle";
14
+ if (ucode == SCARD_E_INSUFFICIENT_BUFFER) return "Insufficient buffer";
15
+ if (ucode == SCARD_E_INVALID_ATR) return "Invalid ATR";
16
+ if (ucode == SCARD_E_INVALID_HANDLE) return "Invalid handle";
17
+ if (ucode == SCARD_E_INVALID_PARAMETER) return "Invalid parameter";
18
+ if (ucode == SCARD_E_INVALID_TARGET) return "Invalid target";
19
+ if (ucode == SCARD_E_INVALID_VALUE) return "Invalid value";
20
+ if (ucode == SCARD_E_NO_MEMORY) return "Not enough memory";
21
+ if (ucode == SCARD_E_NO_SERVICE) return "PC/SC service not running";
22
+ if (ucode == SCARD_E_NO_SMARTCARD) return "No smart card present";
23
+ if (ucode == SCARD_E_NOT_READY) return "Reader not ready";
24
+ if (ucode == SCARD_E_NOT_TRANSACTED) return "Transaction failed";
25
+ if (ucode == SCARD_E_PCI_TOO_SMALL) return "PCI struct too small";
26
+ if (ucode == SCARD_E_PROTO_MISMATCH) return "Protocol mismatch";
27
+ if (ucode == SCARD_E_READER_UNAVAILABLE) return "Reader unavailable";
28
+ if (ucode == SCARD_E_SERVICE_STOPPED) return "PC/SC service stopped";
29
+ if (ucode == SCARD_E_SHARING_VIOLATION) return "Sharing violation";
30
+ if (ucode == SCARD_E_SYSTEM_CANCELLED) return "System cancelled operation";
31
+ if (ucode == SCARD_E_TIMEOUT) return "Operation timed out";
32
+ if (ucode == SCARD_E_UNKNOWN_CARD) return "Unknown card type";
33
+ if (ucode == SCARD_E_UNKNOWN_READER) return "Unknown reader";
34
+ if (ucode == SCARD_E_NO_READERS_AVAILABLE) return "No readers available";
35
+ if (ucode == SCARD_F_COMM_ERROR) return "Communication error";
36
+ if (ucode == SCARD_F_INTERNAL_ERROR) return "Internal error";
37
+ if (ucode == SCARD_W_REMOVED_CARD) return "Card was removed";
38
+ if (ucode == SCARD_W_RESET_CARD) return "Card was reset";
39
+ if (ucode == SCARD_W_UNPOWERED_CARD) return "Card is unpowered";
40
+ if (ucode == SCARD_W_UNRESPONSIVE_CARD) return "Card is unresponsive";
41
+ if (ucode == SCARD_W_UNSUPPORTED_CARD) return "Card is not supported";
42
+
43
+ return "Unknown PC/SC error";
44
+ }
45
+
46
+ // Get the error code value
47
+ inline LONG GetPCSCErrorCode(LONG code) {
48
+ return code;
49
+ }
@@ -0,0 +1,89 @@
1
+ #include "pcsc_reader.h"
2
+ #include "pcsc_card.h"
3
+ #include "pcsc_errors.h"
4
+ #include "async_workers.h"
5
+
6
+ Napi::FunctionReference PCSCReader::constructor;
7
+
8
+ Napi::Object PCSCReader::Init(Napi::Env env, Napi::Object exports) {
9
+ Napi::Function func = DefineClass(env, "Reader", {
10
+ InstanceAccessor("name", &PCSCReader::GetNameValue, nullptr),
11
+ InstanceAccessor("state", &PCSCReader::GetStateValue, nullptr),
12
+ InstanceAccessor("atr", &PCSCReader::GetAtrValue, nullptr),
13
+ InstanceMethod("connect", &PCSCReader::Connect),
14
+ });
15
+
16
+ constructor = Napi::Persistent(func);
17
+ constructor.SuppressDestruct();
18
+
19
+ exports.Set("Reader", func);
20
+ return exports;
21
+ }
22
+
23
+ Napi::Object PCSCReader::NewInstance(Napi::Env env, SCARDCONTEXT context,
24
+ const std::string& name, DWORD state,
25
+ const std::vector<uint8_t>& atr) {
26
+ Napi::Object obj = constructor.New({});
27
+ PCSCReader* reader = Napi::ObjectWrap<PCSCReader>::Unwrap(obj);
28
+ reader->context_ = context;
29
+ reader->name_ = name;
30
+ reader->state_ = state;
31
+ reader->atr_ = atr;
32
+ return obj;
33
+ }
34
+
35
+ PCSCReader::PCSCReader(const Napi::CallbackInfo& info)
36
+ : Napi::ObjectWrap<PCSCReader>(info),
37
+ context_(0),
38
+ state_(SCARD_STATE_UNAWARE) {
39
+ // Properties set via NewInstance
40
+ }
41
+
42
+ void PCSCReader::UpdateState(DWORD state, const std::vector<uint8_t>& atr) {
43
+ state_ = state;
44
+ atr_ = atr;
45
+ }
46
+
47
+ Napi::Value PCSCReader::GetNameValue(const Napi::CallbackInfo& info) {
48
+ return Napi::String::New(info.Env(), name_);
49
+ }
50
+
51
+ Napi::Value PCSCReader::GetStateValue(const Napi::CallbackInfo& info) {
52
+ return Napi::Number::New(info.Env(), state_);
53
+ }
54
+
55
+ Napi::Value PCSCReader::GetAtrValue(const Napi::CallbackInfo& info) {
56
+ Napi::Env env = info.Env();
57
+
58
+ if (atr_.empty()) {
59
+ return env.Null();
60
+ }
61
+
62
+ Napi::Buffer<uint8_t> buffer = Napi::Buffer<uint8_t>::Copy(env, atr_.data(), atr_.size());
63
+ return buffer;
64
+ }
65
+
66
+ Napi::Value PCSCReader::Connect(const Napi::CallbackInfo& info) {
67
+ Napi::Env env = info.Env();
68
+
69
+ // Parse arguments: shareMode (optional), preferredProtocols (optional)
70
+ DWORD shareMode = SCARD_SHARE_SHARED;
71
+ DWORD preferredProtocols = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
72
+
73
+ if (info.Length() > 0 && info[0].IsNumber()) {
74
+ shareMode = info[0].As<Napi::Number>().Uint32Value();
75
+ }
76
+
77
+ if (info.Length() > 1 && info[1].IsNumber()) {
78
+ preferredProtocols = info[1].As<Napi::Number>().Uint32Value();
79
+ }
80
+
81
+ // Create promise for async connection
82
+ Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
83
+
84
+ ConnectWorker* worker = new ConnectWorker(
85
+ env, context_, name_, shareMode, preferredProtocols, deferred);
86
+ worker->Queue();
87
+
88
+ return deferred.Promise();
89
+ }
@@ -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)