diff --git a/README.md b/README.md index 9cb897ad..300f33f9 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,7 @@ pcsc.on('reader', (reader) => { }); - } - else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { + } else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { console.log("card inserted"); diff --git a/binding.gyp b/binding.gyp index 76ba4769..99f5e893 100644 --- a/binding.gyp +++ b/binding.gyp @@ -5,7 +5,8 @@ "sources": [ "src/addon.cpp", "src/pcsclite.cpp", - "src/cardreader.cpp" + "src/cardreader.cpp", + "src/constants.cpp" ], "cflags": [ "-Wall", diff --git a/examples/example.js b/examples/example.js index 75f27847..51de5649 100755 --- a/examples/example.js +++ b/examples/example.js @@ -1,9 +1,16 @@ "use strict"; const pcsclite = require('../lib/pcsclite'); +const { + SCARD_SCOPE_USER, + SCARD_SCOPE_TERMINAL, + SCARD_SCOPE_SYSTEM, + SCARD_SCOPE_GLOBAL, +} = require('../lib/constants'); -const pcsc = pcsclite(); +// const pcsc = pcsclite(); // without options (scope defaults to SCARD_SCOPE_SYSTEM) +const pcsc = pcsclite({ scope: SCARD_SCOPE_USER }); // overwriting default scope pcsc.on('reader', (reader) => { @@ -39,8 +46,7 @@ pcsc.on('reader', (reader) => { }); - } - else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { + } else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { console.log("card inserted"); diff --git a/examples/public.js b/examples/public.js new file mode 100755 index 00000000..a3bc706b --- /dev/null +++ b/examples/public.js @@ -0,0 +1,89 @@ +"use strict"; + +const pcsclite = require('@pokusew/pcsclite'); +const { + SCARD_SCOPE_USER, + SCARD_SCOPE_TERMINAL, + SCARD_SCOPE_SYSTEM, + SCARD_SCOPE_GLOBAL, +} = require('@pokusew/pcsclite/lib/constants'); + + +// const pcsc = pcsclite(); // without options (scope defaults to SCARD_SCOPE_SYSTEM) +const pcsc = pcsclite({ scope: SCARD_SCOPE_USER }); // overwriting default scope + +pcsc.on('reader', (reader) => { + + console.log('New reader detected', reader.name); + + reader.on('error', err => { + console.log('Error(', reader.name, '):', err.message); + }); + + reader.on('status', (status) => { + + console.log('Status(', reader.name, '):', status); + + // check what has changed + const changes = reader.state ^ status.state; + + if (!changes) { + return; + } + + if ((changes & reader.SCARD_STATE_EMPTY) && (status.state & reader.SCARD_STATE_EMPTY)) { + + console.log("card removed"); + + reader.disconnect(reader.SCARD_LEAVE_CARD, err => { + + if (err) { + console.log(err); + return; + } + + console.log('Disconnected'); + + }); + + } else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { + + console.log("card inserted"); + + reader.connect({ share_mode: reader.SCARD_SHARE_SHARED }, (err, protocol) => { + + if (err) { + console.log(err); + return; + } + + console.log('Protocol(', reader.name, '):', protocol); + + reader.transmit(Buffer.from([0x00, 0xB0, 0x00, 0x00, 0x20]), 40, protocol, (err, data) => { + + if (err) { + console.log(err); + return; + } + + console.log('Data received', data); + reader.close(); + pcsc.close(); + + }); + + }); + + } + + }); + + reader.on('end', () => { + console.log('Reader', reader.name, 'removed'); + }); + +}); + +pcsc.on('error', err => { + console.log('PCSC error', err.message); +}); diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..fb1a87d0 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,15 @@ +"use strict"; + +const { + SCARD_SCOPE_USER, + SCARD_SCOPE_TERMINAL, + SCARD_SCOPE_SYSTEM, + SCARD_SCOPE_GLOBAL, +} = require('../build/Release/pcsclite.node'); + +module.exports = { + SCARD_SCOPE_USER, + SCARD_SCOPE_TERMINAL, + SCARD_SCOPE_SYSTEM, + SCARD_SCOPE_GLOBAL, +}; diff --git a/lib/pcsclite.js b/lib/pcsclite.js index da043cc2..af797cc9 100644 --- a/lib/pcsclite.js +++ b/lib/pcsclite.js @@ -8,7 +8,7 @@ const EventEmitter = require('events'); // see https://github.com/nodejs/node-gyp/issues/263, https://github.com/nodejs/node-gyp/issues/631 const pcsclite = require('../build/Release/pcsclite.node'); -const { PCSCLite, CardReader } = pcsclite; +const { PCSCLite, CardReader, SCARD_SCOPE_SYSTEM } = pcsclite; inherits(PCSCLite, EventEmitter); @@ -45,11 +45,16 @@ function diff(a, b) { } -module.exports = function () { +module.exports = function (options = {}) { const readers = {}; - const p = new PCSCLite(); + // const scope = options.scope ?? SCARD_SCOPE_SYSTEM; + // ^ this syntax above (null coalescing operator) would need transpiler as it is not available in the Node.js yet + const scope = options.scope !== undefined && options.scope !== null ? options.scope : SCARD_SCOPE_SYSTEM; + + // TODO: consider alternative approach > allow call with undefined and the C++ side will supply the default value + const p = new PCSCLite(scope); p.readers = readers; @@ -69,7 +74,7 @@ module.exports = function () { newNames.forEach(function (name) { - const r = new CardReader(name); + const r = new CardReader(name, scope); r.on('_end', function () { r.removeAllListeners('status'); diff --git a/src/addon.cpp b/src/addon.cpp index d158b25f..92ee0654 100644 --- a/src/addon.cpp +++ b/src/addon.cpp @@ -1,9 +1,11 @@ #include "pcsclite.h" #include "cardreader.h" +#include "constants.h" void init_all(v8::Local target) { PCSCLite::init(target); CardReader::init(target); + initConstants(target); } NODE_MODULE(pcsclite, init_all) diff --git a/src/cardreader.cpp b/src/cardreader.cpp index 010ea661..a3ce01ea 100644 --- a/src/cardreader.cpp +++ b/src/cardreader.cpp @@ -64,10 +64,11 @@ void CardReader::init(Local target) { Nan::Set(target, Nan::New("CardReader").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); } -CardReader::CardReader(const std::string &reader_name): m_card_context(0), +CardReader::CardReader(const std::string &reader_name, DWORD scope): m_card_context(0), m_card_handle(0), m_name(reader_name), - m_state(0) { + m_state(0), + m_scope(scope) { assert(uv_mutex_init(&m_mutex) == 0); assert(uv_cond_init(&m_cond) == 0); } @@ -91,8 +92,9 @@ NAN_METHOD(CardReader::New) { Nan::HandleScope scope; Nan::Utf8String reader_name(Nan::To(info[0]).ToLocalChecked()); + DWORD pcsc_scope = Nan::To(info[1]).FromJust(); - CardReader* obj = new CardReader(*reader_name); + CardReader* obj = new CardReader(*reader_name, pcsc_scope); obj->Wrap(info.Holder()); Nan::Set(obj->handle(), Nan::New(name_symbol), Nan::To(info[0]).ToLocalChecked()); Nan::Set(obj->handle(), Nan::New(connected_symbol), Nan::False()); @@ -389,7 +391,7 @@ void CardReader::HandlerFunction(void* arg) { async_baton->async_result = new AsyncResult(); async_baton->async_result->do_exit = false; - LONG result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &reader->m_status_card_context); + LONG result = SCardEstablishContext(reader->m_scope, NULL, NULL, &reader->m_status_card_context); SCARD_READERSTATE card_reader_state = SCARD_READERSTATE(); card_reader_state.szReader = reader->m_name.c_str(); @@ -440,7 +442,7 @@ void CardReader::DoConnect(uv_work_t* req) { uv_mutex_lock(&obj->m_mutex); /* Is context established */ if (!obj->m_card_context) { - result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &obj->m_card_context); + result = SCardEstablishContext(obj->m_scope, NULL, NULL, &obj->m_card_context); } /* Connect */ diff --git a/src/cardreader.h b/src/cardreader.h index 7bd239ca..9d105b8b 100644 --- a/src/cardreader.h +++ b/src/cardreader.h @@ -93,7 +93,7 @@ class CardReader: public Nan::ObjectWrap { private: - CardReader(const std::string &reader_name); + CardReader(const std::string &reader_name, DWORD scope); ~CardReader(); @@ -132,6 +132,7 @@ class CardReader: public Nan::ObjectWrap { uv_mutex_t m_mutex; uv_cond_t m_cond; int m_state; + DWORD m_scope; static Nan::AsyncResource *async_resource; }; diff --git a/src/constants.cpp b/src/constants.cpp new file mode 100644 index 00000000..8ac0034a --- /dev/null +++ b/src/constants.cpp @@ -0,0 +1,30 @@ +#include "constants.h" + +#ifdef __APPLE__ +#include +#include +#else +#include +#endif + +void initConstants(v8::Local target) { + // TERMINAL and GLOBAL are not defined on Windows as per + // https://docs.microsoft.com/windows/win32/api/winscard/nf-winscard-scardestablishcontext + + Nan::Set(target, Nan::New("SCARD_SCOPE_USER").ToLocalChecked(), Nan::New(SCARD_SCOPE_USER)); + +#ifdef WIN32 + Nan::Set(target, Nan::New("SCARD_SCOPE_TERMINAL").ToLocalChecked(), Nan::New(SCARD_SCOPE_USER)); +#else + Nan::Set(target, Nan::New("SCARD_SCOPE_TERMINAL").ToLocalChecked(), Nan::New(SCARD_SCOPE_TERMINAL)); +#endif /* WIN32 */ + + Nan::Set(target, Nan::New("SCARD_SCOPE_SYSTEM").ToLocalChecked(), Nan::New(SCARD_SCOPE_SYSTEM)); + +#ifdef WIN32 + Nan::Set(target, Nan::New("SCARD_SCOPE_GLOBAL").ToLocalChecked(), Nan::New(SCARD_SCOPE_SYSTEM)); +#else + Nan::Set(target, Nan::New("SCARD_SCOPE_GLOBAL").ToLocalChecked(), Nan::New(SCARD_SCOPE_GLOBAL)); +#endif /* WIN32 */ + +} diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 00000000..57725cf5 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,7 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H +#include + +void initConstants(v8::Local target); + +#endif /* CONSTANTS_H */ diff --git a/src/pcsclite.cpp b/src/pcsclite.cpp index dd66881c..8152f973 100644 --- a/src/pcsclite.cpp +++ b/src/pcsclite.cpp @@ -23,10 +23,11 @@ void PCSCLite::init(Local target) { Nan::Set(target, Nan::New("PCSCLite").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); } -PCSCLite::PCSCLite(): m_card_context(0), +PCSCLite::PCSCLite(DWORD scope): m_card_context(0), m_card_reader_state(), m_status_thread(0), - m_state(0) { + m_state(0), + m_scope(scope) { assert(uv_mutex_init(&m_mutex) == 0); assert(uv_cond_init(&m_cond) == 0); @@ -70,8 +71,7 @@ PCSCLite::PCSCLite(): m_card_context(0), LONG result; // TODO: consider removing this do-while Windows workaround that should not be needed anymore do { - // TODO: make dwScope (now hard-coded to SCARD_SCOPE_SYSTEM) customisable - result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, + result = SCardEstablishContext(m_scope, NULL, NULL, &m_card_context); @@ -111,7 +111,11 @@ PCSCLite::~PCSCLite() { NAN_METHOD(PCSCLite::New) { Nan::HandleScope scope; - PCSCLite* obj = new PCSCLite(); + + DWORD pcsc_scope = Nan::To(info[0]).FromJust(); + + PCSCLite* obj = new PCSCLite(pcsc_scope); + obj->Wrap(info.Holder()); info.GetReturnValue().Set(info.Holder()); } @@ -340,7 +344,7 @@ LONG PCSCLite::get_card_readers(PCSCLite* pcsclite, AsyncResult* async_result) { #endif if (result == SCARD_E_NO_SERVICE || result == SCARD_E_SERVICE_STOPPED) { SCardReleaseContext(pcsclite->m_card_context); - SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &pcsclite->m_card_context); + SCardEstablishContext(pcsclite->m_scope, NULL, NULL, &pcsclite->m_card_context); result = get_card_readers(pcsclite, async_result); } } else { diff --git a/src/pcsclite.h b/src/pcsclite.h index 22fcd3a8..457fd388 100644 --- a/src/pcsclite.h +++ b/src/pcsclite.h @@ -32,7 +32,7 @@ class PCSCLite: public Nan::ObjectWrap { private: - PCSCLite(); + PCSCLite(DWORD scope); ~PCSCLite(); @@ -56,6 +56,7 @@ class PCSCLite: public Nan::ObjectWrap { uv_cond_t m_cond; bool m_pnp; int m_state; + DWORD m_scope; static Nan::AsyncResource *async_resource; };