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,248 @@
|
|
|
1
|
+
#include "pcsc_card.h"
|
|
2
|
+
#include "pcsc_errors.h"
|
|
3
|
+
#include "async_workers.h"
|
|
4
|
+
#include <cstring>
|
|
5
|
+
|
|
6
|
+
Napi::FunctionReference PCSCCard::constructor;
|
|
7
|
+
|
|
8
|
+
Napi::Object PCSCCard::Init(Napi::Env env, Napi::Object exports) {
|
|
9
|
+
Napi::Function func = DefineClass(env, "Card", {
|
|
10
|
+
InstanceAccessor("protocol", &PCSCCard::GetProtocolValue, nullptr),
|
|
11
|
+
InstanceAccessor("connected", &PCSCCard::GetConnectedValue, nullptr),
|
|
12
|
+
InstanceAccessor("atr", &PCSCCard::GetAtr, nullptr),
|
|
13
|
+
InstanceMethod("transmit", &PCSCCard::Transmit),
|
|
14
|
+
InstanceMethod("control", &PCSCCard::Control),
|
|
15
|
+
InstanceMethod("getStatus", &PCSCCard::GetStatus),
|
|
16
|
+
InstanceMethod("disconnect", &PCSCCard::Disconnect),
|
|
17
|
+
InstanceMethod("reconnect", &PCSCCard::Reconnect),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
constructor = Napi::Persistent(func);
|
|
21
|
+
constructor.SuppressDestruct();
|
|
22
|
+
|
|
23
|
+
exports.Set("Card", func);
|
|
24
|
+
return exports;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Napi::Object PCSCCard::NewInstance(Napi::Env env, SCARDHANDLE card,
|
|
28
|
+
DWORD protocol, const std::string& readerName) {
|
|
29
|
+
Napi::Object obj = constructor.New({});
|
|
30
|
+
PCSCCard* cardObj = Napi::ObjectWrap<PCSCCard>::Unwrap(obj);
|
|
31
|
+
cardObj->card_ = card;
|
|
32
|
+
cardObj->protocol_ = protocol;
|
|
33
|
+
cardObj->readerName_ = readerName;
|
|
34
|
+
cardObj->connected_ = true;
|
|
35
|
+
return obj;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
PCSCCard::PCSCCard(const Napi::CallbackInfo& info)
|
|
39
|
+
: Napi::ObjectWrap<PCSCCard>(info),
|
|
40
|
+
card_(0),
|
|
41
|
+
protocol_(SCARD_PROTOCOL_UNDEFINED),
|
|
42
|
+
connected_(false) {
|
|
43
|
+
// Properties set via NewInstance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
PCSCCard::~PCSCCard() {
|
|
47
|
+
if (connected_ && card_ != 0) {
|
|
48
|
+
SCardDisconnect(card_, SCARD_LEAVE_CARD);
|
|
49
|
+
connected_ = false;
|
|
50
|
+
card_ = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Napi::Value PCSCCard::GetProtocolValue(const Napi::CallbackInfo& info) {
|
|
55
|
+
return Napi::Number::New(info.Env(), protocol_);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Napi::Value PCSCCard::GetConnectedValue(const Napi::CallbackInfo& info) {
|
|
59
|
+
return Napi::Boolean::New(info.Env(), connected_);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Napi::Value PCSCCard::GetAtr(const Napi::CallbackInfo& info) {
|
|
63
|
+
Napi::Env env = info.Env();
|
|
64
|
+
|
|
65
|
+
if (!connected_) {
|
|
66
|
+
return env.Null();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get ATR via SCardStatus
|
|
70
|
+
DWORD readerLen = 0;
|
|
71
|
+
DWORD state = 0;
|
|
72
|
+
DWORD protocol = 0;
|
|
73
|
+
BYTE atr[MAX_ATR_SIZE];
|
|
74
|
+
DWORD atrLen = sizeof(atr);
|
|
75
|
+
|
|
76
|
+
LONG result = SCardStatus(card_, nullptr, &readerLen, &state, &protocol, atr, &atrLen);
|
|
77
|
+
|
|
78
|
+
if (result != SCARD_S_SUCCESS) {
|
|
79
|
+
return env.Null();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Napi::Buffer<uint8_t>::Copy(env, atr, atrLen);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Napi::Value PCSCCard::Transmit(const Napi::CallbackInfo& info) {
|
|
86
|
+
Napi::Env env = info.Env();
|
|
87
|
+
|
|
88
|
+
if (!connected_) {
|
|
89
|
+
Napi::Error::New(env, "Card is not connected").ThrowAsJavaScriptException();
|
|
90
|
+
return env.Null();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (info.Length() < 1) {
|
|
94
|
+
Napi::TypeError::New(env, "Expected command buffer").ThrowAsJavaScriptException();
|
|
95
|
+
return env.Null();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
std::vector<uint8_t> sendBuffer;
|
|
99
|
+
|
|
100
|
+
if (info[0].IsBuffer()) {
|
|
101
|
+
Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
|
|
102
|
+
sendBuffer.assign(buffer.Data(), buffer.Data() + buffer.Length());
|
|
103
|
+
} else if (info[0].IsArray()) {
|
|
104
|
+
Napi::Array arr = info[0].As<Napi::Array>();
|
|
105
|
+
sendBuffer.reserve(arr.Length());
|
|
106
|
+
for (uint32_t i = 0; i < arr.Length(); i++) {
|
|
107
|
+
sendBuffer.push_back(static_cast<uint8_t>(arr.Get(i).As<Napi::Number>().Uint32Value()));
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
Napi::TypeError::New(env, "Expected Buffer or Array").ThrowAsJavaScriptException();
|
|
111
|
+
return env.Null();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create promise for async transmit
|
|
115
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
116
|
+
|
|
117
|
+
TransmitWorker* worker = new TransmitWorker(
|
|
118
|
+
env, card_, protocol_, sendBuffer, deferred);
|
|
119
|
+
worker->Queue();
|
|
120
|
+
|
|
121
|
+
return deferred.Promise();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Napi::Value PCSCCard::Control(const Napi::CallbackInfo& info) {
|
|
125
|
+
Napi::Env env = info.Env();
|
|
126
|
+
|
|
127
|
+
if (!connected_) {
|
|
128
|
+
Napi::Error::New(env, "Card is not connected").ThrowAsJavaScriptException();
|
|
129
|
+
return env.Null();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
133
|
+
Napi::TypeError::New(env, "Expected control code").ThrowAsJavaScriptException();
|
|
134
|
+
return env.Null();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
DWORD controlCode = info[0].As<Napi::Number>().Uint32Value();
|
|
138
|
+
|
|
139
|
+
std::vector<uint8_t> sendBuffer;
|
|
140
|
+
if (info.Length() > 1) {
|
|
141
|
+
if (info[1].IsBuffer()) {
|
|
142
|
+
Napi::Buffer<uint8_t> buffer = info[1].As<Napi::Buffer<uint8_t>>();
|
|
143
|
+
sendBuffer.assign(buffer.Data(), buffer.Data() + buffer.Length());
|
|
144
|
+
} else if (info[1].IsArray()) {
|
|
145
|
+
Napi::Array arr = info[1].As<Napi::Array>();
|
|
146
|
+
sendBuffer.reserve(arr.Length());
|
|
147
|
+
for (uint32_t i = 0; i < arr.Length(); i++) {
|
|
148
|
+
sendBuffer.push_back(static_cast<uint8_t>(arr.Get(i).As<Napi::Number>().Uint32Value()));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create promise for async control
|
|
154
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
155
|
+
|
|
156
|
+
ControlWorker* worker = new ControlWorker(
|
|
157
|
+
env, card_, controlCode, sendBuffer, deferred);
|
|
158
|
+
worker->Queue();
|
|
159
|
+
|
|
160
|
+
return deferred.Promise();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Napi::Value PCSCCard::GetStatus(const Napi::CallbackInfo& info) {
|
|
164
|
+
Napi::Env env = info.Env();
|
|
165
|
+
|
|
166
|
+
if (!connected_) {
|
|
167
|
+
Napi::Error::New(env, "Card is not connected").ThrowAsJavaScriptException();
|
|
168
|
+
return env.Null();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
DWORD readerLen = 0;
|
|
172
|
+
DWORD state = 0;
|
|
173
|
+
DWORD protocol = 0;
|
|
174
|
+
BYTE atr[MAX_ATR_SIZE];
|
|
175
|
+
DWORD atrLen = sizeof(atr);
|
|
176
|
+
|
|
177
|
+
// First call to get reader name length
|
|
178
|
+
LONG result = SCardStatus(card_, nullptr, &readerLen, &state, &protocol, atr, &atrLen);
|
|
179
|
+
|
|
180
|
+
if (result != SCARD_S_SUCCESS) {
|
|
181
|
+
Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
|
|
182
|
+
return env.Null();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Napi::Object status = Napi::Object::New(env);
|
|
186
|
+
status.Set("state", Napi::Number::New(env, state));
|
|
187
|
+
status.Set("protocol", Napi::Number::New(env, protocol));
|
|
188
|
+
status.Set("atr", Napi::Buffer<uint8_t>::Copy(env, atr, atrLen));
|
|
189
|
+
|
|
190
|
+
return status;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
Napi::Value PCSCCard::Disconnect(const Napi::CallbackInfo& info) {
|
|
194
|
+
Napi::Env env = info.Env();
|
|
195
|
+
|
|
196
|
+
if (!connected_) {
|
|
197
|
+
return env.Undefined();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
DWORD disposition = SCARD_LEAVE_CARD;
|
|
201
|
+
if (info.Length() > 0 && info[0].IsNumber()) {
|
|
202
|
+
disposition = info[0].As<Napi::Number>().Uint32Value();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
LONG result = SCardDisconnect(card_, disposition);
|
|
206
|
+
connected_ = false;
|
|
207
|
+
card_ = 0;
|
|
208
|
+
|
|
209
|
+
if (result != SCARD_S_SUCCESS) {
|
|
210
|
+
Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return env.Undefined();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Napi::Value PCSCCard::Reconnect(const Napi::CallbackInfo& info) {
|
|
217
|
+
Napi::Env env = info.Env();
|
|
218
|
+
|
|
219
|
+
if (!connected_) {
|
|
220
|
+
Napi::Error::New(env, "Card is not connected").ThrowAsJavaScriptException();
|
|
221
|
+
return env.Null();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
DWORD shareMode = SCARD_SHARE_SHARED;
|
|
225
|
+
DWORD preferredProtocols = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
|
|
226
|
+
DWORD initialization = SCARD_LEAVE_CARD;
|
|
227
|
+
|
|
228
|
+
if (info.Length() > 0 && info[0].IsNumber()) {
|
|
229
|
+
shareMode = info[0].As<Napi::Number>().Uint32Value();
|
|
230
|
+
}
|
|
231
|
+
if (info.Length() > 1 && info[1].IsNumber()) {
|
|
232
|
+
preferredProtocols = info[1].As<Napi::Number>().Uint32Value();
|
|
233
|
+
}
|
|
234
|
+
if (info.Length() > 2 && info[2].IsNumber()) {
|
|
235
|
+
initialization = info[2].As<Napi::Number>().Uint32Value();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
DWORD activeProtocol = 0;
|
|
239
|
+
LONG result = SCardReconnect(card_, shareMode, preferredProtocols, initialization, &activeProtocol);
|
|
240
|
+
|
|
241
|
+
if (result != SCARD_S_SUCCESS) {
|
|
242
|
+
Napi::Error::New(env, GetPCSCErrorString(result)).ThrowAsJavaScriptException();
|
|
243
|
+
return env.Null();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
protocol_ = activeProtocol;
|
|
247
|
+
return Napi::Number::New(env, activeProtocol);
|
|
248
|
+
}
|
package/src/pcsc_card.h
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include "platform/pcsc.h"
|
|
7
|
+
|
|
8
|
+
class PCSCCard : public Napi::ObjectWrap<PCSCCard> {
|
|
9
|
+
public:
|
|
10
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
11
|
+
static Napi::Object NewInstance(Napi::Env env, SCARDHANDLE card,
|
|
12
|
+
DWORD protocol, const std::string& readerName);
|
|
13
|
+
|
|
14
|
+
PCSCCard(const Napi::CallbackInfo& info);
|
|
15
|
+
~PCSCCard();
|
|
16
|
+
|
|
17
|
+
// Accessors
|
|
18
|
+
SCARDHANDLE GetHandle() const { return card_; }
|
|
19
|
+
DWORD GetProtocol() const { return protocol_; }
|
|
20
|
+
bool IsConnected() const { return connected_; }
|
|
21
|
+
|
|
22
|
+
private:
|
|
23
|
+
static Napi::FunctionReference constructor;
|
|
24
|
+
|
|
25
|
+
SCARDHANDLE card_;
|
|
26
|
+
DWORD protocol_;
|
|
27
|
+
std::string readerName_;
|
|
28
|
+
bool connected_;
|
|
29
|
+
|
|
30
|
+
// JavaScript-exposed methods
|
|
31
|
+
Napi::Value Transmit(const Napi::CallbackInfo& info);
|
|
32
|
+
Napi::Value Control(const Napi::CallbackInfo& info);
|
|
33
|
+
Napi::Value GetStatus(const Napi::CallbackInfo& info);
|
|
34
|
+
Napi::Value Disconnect(const Napi::CallbackInfo& info);
|
|
35
|
+
Napi::Value Reconnect(const Napi::CallbackInfo& info);
|
|
36
|
+
|
|
37
|
+
// Getters
|
|
38
|
+
Napi::Value GetProtocolValue(const Napi::CallbackInfo& info);
|
|
39
|
+
Napi::Value GetConnectedValue(const Napi::CallbackInfo& info);
|
|
40
|
+
Napi::Value GetAtr(const Napi::CallbackInfo& info);
|
|
41
|
+
};
|
|
@@ -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
|
+
}
|