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/lib/devices.js ADDED
@@ -0,0 +1,283 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ const EventEmitter = require('events');
5
+ const addon = require('../build/Release/smartcard_napi.node');
6
+
7
+ /**
8
+ * @typedef {import('./index').Reader} Reader
9
+ * @typedef {import('./index').Card} Card
10
+ * @typedef {import('./index').MonitorEvent} MonitorEvent
11
+ * @typedef {import('./index').Context} ContextType
12
+ * @typedef {import('./index').ReaderMonitor} ReaderMonitorType
13
+ */
14
+
15
+ const { Context, ReaderMonitor } = addon;
16
+ const SCARD_STATE_PRESENT = addon.SCARD_STATE_PRESENT;
17
+ const SCARD_SHARE_SHARED = addon.SCARD_SHARE_SHARED;
18
+ const SCARD_PROTOCOL_T0 = addon.SCARD_PROTOCOL_T0;
19
+ const SCARD_PROTOCOL_T1 = addon.SCARD_PROTOCOL_T1;
20
+
21
+ /**
22
+ * @typedef {Object} ReaderState
23
+ * @property {boolean} hasCard
24
+ * @property {Card|null} card
25
+ */
26
+
27
+ /**
28
+ * High-level event-driven API for PC/SC devices
29
+ *
30
+ * Uses native ReaderMonitor for efficient background monitoring
31
+ * with ThreadSafeFunction to emit events from worker thread.
32
+ *
33
+ * Events:
34
+ * - 'reader-attached': Emitted when a reader is attached
35
+ * - 'reader-detached': Emitted when a reader is detached
36
+ * - 'card-inserted': Emitted when a card is inserted
37
+ * - 'card-removed': Emitted when a card is removed
38
+ * - 'error': Emitted on errors
39
+ *
40
+ * @extends EventEmitter
41
+ */
42
+ class Devices extends EventEmitter {
43
+ constructor() {
44
+ super();
45
+ /** @type {ReaderMonitorType|null} */
46
+ this._monitor = null;
47
+ /** @type {ContextType|null} */
48
+ this._context = null;
49
+ /** @type {boolean} */
50
+ this._running = false;
51
+ /** @type {Map<string, ReaderState>} */
52
+ this._readers = new Map();
53
+ }
54
+
55
+ /**
56
+ * Start monitoring for device changes
57
+ */
58
+ start() {
59
+ if (this._running) {
60
+ return;
61
+ }
62
+
63
+ try {
64
+ // Create context for card connections
65
+ this._context = new Context();
66
+
67
+ // Create native monitor
68
+ this._monitor = new ReaderMonitor();
69
+ this._running = true;
70
+
71
+ // Start native monitoring with callback
72
+ this._monitor.start((event) => {
73
+ this._handleEvent(event);
74
+ });
75
+ } catch (err) {
76
+ this.emit('error', err);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Stop monitoring
82
+ */
83
+ stop() {
84
+ this._running = false;
85
+
86
+ if (this._monitor) {
87
+ try {
88
+ this._monitor.stop();
89
+ } catch (err) {
90
+ // Ignore stop errors
91
+ }
92
+ this._monitor = null;
93
+ }
94
+
95
+ // Disconnect any connected cards
96
+ for (const [name, state] of this._readers) {
97
+ if (state.card) {
98
+ try {
99
+ state.card.disconnect();
100
+ } catch (err) {
101
+ // Ignore disconnect errors
102
+ }
103
+ }
104
+ }
105
+ this._readers.clear();
106
+
107
+ if (this._context) {
108
+ try {
109
+ this._context.close();
110
+ } catch (err) {
111
+ // Ignore close errors
112
+ }
113
+ this._context = null;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * List currently known readers
119
+ * @returns {Reader[]} Array of readers
120
+ */
121
+ listReaders() {
122
+ if (!this._context || !this._context.isValid) {
123
+ return [];
124
+ }
125
+ try {
126
+ return this._context.listReaders();
127
+ } catch (err) {
128
+ return [];
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Handle events from native monitor
134
+ * @param {MonitorEvent} event
135
+ * @returns {Promise<void>}
136
+ */
137
+ async _handleEvent(event) {
138
+ if (!this._running) {
139
+ return;
140
+ }
141
+
142
+ const { type, reader: readerName, state, atr } = event;
143
+
144
+ switch (type) {
145
+ case 'reader-attached':
146
+ this._handleReaderAttached(readerName, state, atr);
147
+ break;
148
+
149
+ case 'reader-detached':
150
+ this._handleReaderDetached(readerName);
151
+ break;
152
+
153
+ case 'card-inserted':
154
+ await this._handleCardInserted(readerName, state, atr);
155
+ break;
156
+
157
+ case 'card-removed':
158
+ this._handleCardRemoved(readerName);
159
+ break;
160
+
161
+ case 'error':
162
+ // readerName contains error message for error events
163
+ this.emit('error', new Error(readerName));
164
+ break;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Handle reader attached
170
+ * @param {string} readerName
171
+ * @param {number} state
172
+ * @param {Buffer|null} atr
173
+ */
174
+ _handleReaderAttached(readerName, state, atr) {
175
+ // Initialize reader state
176
+ this._readers.set(readerName, {
177
+ hasCard: false,
178
+ card: null,
179
+ });
180
+
181
+ // Create a reader-like object for the event
182
+ const reader = {
183
+ name: readerName,
184
+ state: state,
185
+ atr: atr,
186
+ };
187
+
188
+ this.emit('reader-attached', reader);
189
+
190
+ // Check if card is already present
191
+ if ((state & SCARD_STATE_PRESENT) !== 0) {
192
+ this._handleCardInserted(readerName, state, atr);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Handle reader detached
198
+ * @param {string} readerName
199
+ */
200
+ _handleReaderDetached(readerName) {
201
+ const state = this._readers.get(readerName);
202
+
203
+ // If card was connected, emit card-removed first
204
+ if (state && state.hasCard) {
205
+ this._handleCardRemoved(readerName);
206
+ }
207
+
208
+ this._readers.delete(readerName);
209
+
210
+ const reader = { name: readerName };
211
+ this.emit('reader-detached', reader);
212
+ }
213
+
214
+ /**
215
+ * Handle card inserted
216
+ * @param {string} readerName
217
+ * @param {number} eventState
218
+ * @param {Buffer|null} atr
219
+ * @returns {Promise<void>}
220
+ */
221
+ async _handleCardInserted(readerName, eventState, atr) {
222
+ let state = this._readers.get(readerName);
223
+ if (!state) {
224
+ state = { hasCard: false, card: null };
225
+ this._readers.set(readerName, state);
226
+ }
227
+
228
+ state.hasCard = true;
229
+
230
+ // Try to connect to the card
231
+ try {
232
+ const readers = this._context.listReaders();
233
+ const reader = readers.find(r => r.name === readerName);
234
+
235
+ if (reader) {
236
+ const card = await reader.connect(
237
+ SCARD_SHARE_SHARED,
238
+ SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1
239
+ );
240
+
241
+ state.card = card;
242
+
243
+ this.emit('card-inserted', {
244
+ reader: { name: readerName, state: eventState, atr: atr },
245
+ card: card,
246
+ });
247
+ }
248
+ } catch (err) {
249
+ // Emit error but don't fail
250
+ this.emit('error', err);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Handle card removed
256
+ * @param {string} readerName
257
+ */
258
+ _handleCardRemoved(readerName) {
259
+ const state = this._readers.get(readerName);
260
+ if (!state) {
261
+ return;
262
+ }
263
+
264
+ const card = state.card;
265
+ state.hasCard = false;
266
+ state.card = null;
267
+
268
+ if (card) {
269
+ try {
270
+ card.disconnect();
271
+ } catch (err) {
272
+ // Ignore - card is already removed
273
+ }
274
+ }
275
+
276
+ this.emit('card-removed', {
277
+ reader: { name: readerName },
278
+ card: card,
279
+ });
280
+ }
281
+ }
282
+
283
+ module.exports = { Devices };
package/lib/errors.js ADDED
@@ -0,0 +1,77 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ /**
5
+ * Base error class for PC/SC errors
6
+ */
7
+ class PCSCError extends Error {
8
+ /**
9
+ * @param {string} message
10
+ * @param {number} code
11
+ */
12
+ constructor(message, code) {
13
+ super(message);
14
+ this.name = 'PCSCError';
15
+ this.code = code;
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Error thrown when a card is removed during an operation
22
+ */
23
+ class CardRemovedError extends PCSCError {
24
+ constructor(message = 'Card was removed') {
25
+ super(message, 0x80100069);
26
+ this.name = 'CardRemovedError';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Error thrown when an operation times out
32
+ */
33
+ class TimeoutError extends PCSCError {
34
+ constructor(message = 'Operation timed out') {
35
+ super(message, 0x8010000A);
36
+ this.name = 'TimeoutError';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Error thrown when no readers are available
42
+ */
43
+ class NoReadersError extends PCSCError {
44
+ constructor(message = 'No readers available') {
45
+ super(message, 0x8010002E);
46
+ this.name = 'NoReadersError';
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Error thrown when PC/SC service is not running
52
+ */
53
+ class ServiceNotRunningError extends PCSCError {
54
+ constructor(message = 'PC/SC service not running') {
55
+ super(message, 0x8010001D);
56
+ this.name = 'ServiceNotRunningError';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Error thrown when there's a sharing violation
62
+ */
63
+ class SharingViolationError extends PCSCError {
64
+ constructor(message = 'Sharing violation - card is in use') {
65
+ super(message, 0x8010000B);
66
+ this.name = 'SharingViolationError';
67
+ }
68
+ }
69
+
70
+ module.exports = {
71
+ PCSCError,
72
+ CardRemovedError,
73
+ TimeoutError,
74
+ NoReadersError,
75
+ ServiceNotRunningError,
76
+ SharingViolationError,
77
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,247 @@
1
+ /// <reference types="node" />
2
+
3
+ import { EventEmitter } from 'events';
4
+
5
+ /**
6
+ * Reader state information returned from waitForChange
7
+ */
8
+ export interface ReaderState {
9
+ name: string;
10
+ state: number;
11
+ changed: boolean;
12
+ atr: Buffer | null;
13
+ }
14
+
15
+ /**
16
+ * Card status information
17
+ */
18
+ export interface CardStatus {
19
+ state: number;
20
+ protocol: number;
21
+ atr: Buffer;
22
+ }
23
+
24
+ /**
25
+ * Represents a connected smart card
26
+ */
27
+ export interface Card {
28
+ /** The active protocol (T0, T1, or RAW) */
29
+ readonly protocol: number;
30
+ /** Whether the card is still connected */
31
+ readonly connected: boolean;
32
+ /** The card's ATR (Answer To Reset) */
33
+ readonly atr: Buffer | null;
34
+
35
+ /**
36
+ * Transmit an APDU command to the card
37
+ * @param command - The command buffer or byte array
38
+ * @returns Promise resolving to the response buffer
39
+ */
40
+ transmit(command: Buffer | number[]): Promise<Buffer>;
41
+
42
+ /**
43
+ * Send a control command to the reader
44
+ * @param code - Control code
45
+ * @param data - Optional data buffer
46
+ * @returns Promise resolving to the response buffer
47
+ */
48
+ control(code: number, data?: Buffer | number[]): Promise<Buffer>;
49
+
50
+ /**
51
+ * Get the current card status
52
+ * @returns Card status object
53
+ */
54
+ getStatus(): CardStatus;
55
+
56
+ /**
57
+ * Disconnect from the card
58
+ * @param disposition - What to do with the card (default: SCARD_LEAVE_CARD)
59
+ */
60
+ disconnect(disposition?: number): void;
61
+
62
+ /**
63
+ * Reconnect to the card
64
+ * @param shareMode - Share mode
65
+ * @param protocol - Preferred protocol(s)
66
+ * @param initialization - Initialization action
67
+ * @returns The new active protocol
68
+ */
69
+ reconnect(shareMode?: number, protocol?: number, initialization?: number): number;
70
+ }
71
+
72
+ /**
73
+ * Represents a smart card reader
74
+ */
75
+ export interface Reader {
76
+ /** The reader name */
77
+ readonly name: string;
78
+ /** Current reader state flags */
79
+ readonly state: number;
80
+ /** ATR of the card if present */
81
+ readonly atr: Buffer | null;
82
+
83
+ /**
84
+ * Connect to a card in the reader
85
+ * @param shareMode - Share mode (default: SCARD_SHARE_SHARED)
86
+ * @param protocol - Preferred protocol(s) (default: T0 | T1)
87
+ * @returns Promise resolving to a Card object
88
+ */
89
+ connect(shareMode?: number, protocol?: number): Promise<Card>;
90
+ }
91
+
92
+ /**
93
+ * Low-level PC/SC context
94
+ */
95
+ export declare class Context {
96
+ constructor();
97
+
98
+ /** Whether the context is still valid */
99
+ readonly isValid: boolean;
100
+
101
+ /**
102
+ * List available readers
103
+ * @returns Array of Reader objects
104
+ */
105
+ listReaders(): Reader[];
106
+
107
+ /**
108
+ * Wait for reader/card state changes
109
+ * @param readers - Optional array of readers to monitor
110
+ * @param timeout - Timeout in milliseconds (default: infinite)
111
+ * @returns Promise resolving to array of reader states
112
+ */
113
+ waitForChange(readers?: Reader[] | ReaderState[], timeout?: number): Promise<ReaderState[] | null>;
114
+
115
+ /**
116
+ * Cancel a pending waitForChange call
117
+ */
118
+ cancel(): void;
119
+
120
+ /**
121
+ * Close the context and release resources
122
+ */
123
+ close(): void;
124
+ }
125
+
126
+ /**
127
+ * Monitor event from native ReaderMonitor
128
+ */
129
+ export interface MonitorEvent {
130
+ type: 'reader-attached' | 'reader-detached' | 'card-inserted' | 'card-removed' | 'error';
131
+ reader: string;
132
+ state: number;
133
+ atr: Buffer | null;
134
+ }
135
+
136
+ /**
137
+ * Native PC/SC event monitor using ThreadSafeFunction
138
+ * Runs monitoring on a background thread for efficiency
139
+ */
140
+ export declare class ReaderMonitor {
141
+ constructor();
142
+
143
+ /** Whether the monitor is currently running */
144
+ readonly isRunning: boolean;
145
+
146
+ /**
147
+ * Start monitoring for reader/card changes
148
+ * @param callback - Function called when events occur
149
+ */
150
+ start(callback: (event: MonitorEvent) => void): void;
151
+
152
+ /**
153
+ * Stop monitoring
154
+ */
155
+ stop(): void;
156
+ }
157
+
158
+ /**
159
+ * Event types for Devices class
160
+ */
161
+ export interface DeviceEvents {
162
+ 'reader-attached': (reader: Reader) => void;
163
+ 'reader-detached': (reader: Reader) => void;
164
+ 'card-inserted': (event: { reader: Reader; card: Card }) => void;
165
+ 'card-removed': (event: { reader: Reader; card: Card | null }) => void;
166
+ 'error': (error: Error) => void;
167
+ }
168
+
169
+ /**
170
+ * High-level event-driven API for monitoring PC/SC devices
171
+ */
172
+ export declare class Devices extends EventEmitter {
173
+ constructor();
174
+
175
+ /**
176
+ * Start monitoring for device changes
177
+ */
178
+ start(): void;
179
+
180
+ /**
181
+ * Stop monitoring and release resources
182
+ */
183
+ stop(): void;
184
+
185
+ /**
186
+ * List currently known readers
187
+ * @returns Array of Reader objects
188
+ */
189
+ listReaders(): Reader[];
190
+
191
+ on<K extends keyof DeviceEvents>(event: K, listener: DeviceEvents[K]): this;
192
+ once<K extends keyof DeviceEvents>(event: K, listener: DeviceEvents[K]): this;
193
+ off<K extends keyof DeviceEvents>(event: K, listener: DeviceEvents[K]): this;
194
+ emit<K extends keyof DeviceEvents>(event: K, ...args: Parameters<DeviceEvents[K]>): boolean;
195
+ }
196
+
197
+ /**
198
+ * Base PC/SC error class
199
+ */
200
+ export declare class PCSCError extends Error {
201
+ readonly code: number;
202
+ constructor(message: string, code: number);
203
+ }
204
+
205
+ /**
206
+ * Error thrown when card is removed during operation
207
+ */
208
+ export declare class CardRemovedError extends PCSCError {
209
+ constructor(message?: string);
210
+ }
211
+
212
+ /**
213
+ * Error thrown when operation times out
214
+ */
215
+ export declare class TimeoutError extends PCSCError {
216
+ constructor(message?: string);
217
+ }
218
+
219
+ // Share modes
220
+ export declare const SCARD_SHARE_EXCLUSIVE: number;
221
+ export declare const SCARD_SHARE_SHARED: number;
222
+ export declare const SCARD_SHARE_DIRECT: number;
223
+
224
+ // Protocols
225
+ export declare const SCARD_PROTOCOL_T0: number;
226
+ export declare const SCARD_PROTOCOL_T1: number;
227
+ export declare const SCARD_PROTOCOL_RAW: number;
228
+ export declare const SCARD_PROTOCOL_UNDEFINED: number;
229
+
230
+ // Disposition
231
+ export declare const SCARD_LEAVE_CARD: number;
232
+ export declare const SCARD_RESET_CARD: number;
233
+ export declare const SCARD_UNPOWER_CARD: number;
234
+ export declare const SCARD_EJECT_CARD: number;
235
+
236
+ // State flags
237
+ export declare const SCARD_STATE_UNAWARE: number;
238
+ export declare const SCARD_STATE_IGNORE: number;
239
+ export declare const SCARD_STATE_CHANGED: number;
240
+ export declare const SCARD_STATE_UNKNOWN: number;
241
+ export declare const SCARD_STATE_UNAVAILABLE: number;
242
+ export declare const SCARD_STATE_EMPTY: number;
243
+ export declare const SCARD_STATE_PRESENT: number;
244
+ export declare const SCARD_STATE_ATRMATCH: number;
245
+ export declare const SCARD_STATE_EXCLUSIVE: number;
246
+ export declare const SCARD_STATE_INUSE: number;
247
+ export declare const SCARD_STATE_MUTE: number;
package/lib/index.js CHANGED
@@ -1,24 +1,85 @@
1
+ // @ts-check
1
2
  'use strict';
2
3
 
3
- var _Iso7816Application = _interopRequireDefault(require("./Iso7816Application"));
4
+ // Load native addon
5
+ const addon = require('../build/Release/smartcard_napi.node');
4
6
 
5
- var _CommandApdu = _interopRequireDefault(require("./CommandApdu"));
7
+ // Re-export native classes
8
+ const { Context, Reader, Card, ReaderMonitor } = addon;
6
9
 
7
- var _ResponseApdu = _interopRequireDefault(require("./ResponseApdu"));
10
+ // Re-export constants
11
+ const SCARD_SHARE_EXCLUSIVE = addon.SCARD_SHARE_EXCLUSIVE;
12
+ const SCARD_SHARE_SHARED = addon.SCARD_SHARE_SHARED;
13
+ const SCARD_SHARE_DIRECT = addon.SCARD_SHARE_DIRECT;
8
14
 
9
- var _Devices = _interopRequireDefault(require("./Devices"));
15
+ const SCARD_PROTOCOL_T0 = addon.SCARD_PROTOCOL_T0;
16
+ const SCARD_PROTOCOL_T1 = addon.SCARD_PROTOCOL_T1;
17
+ const SCARD_PROTOCOL_RAW = addon.SCARD_PROTOCOL_RAW;
18
+ const SCARD_PROTOCOL_UNDEFINED = addon.SCARD_PROTOCOL_UNDEFINED;
10
19
 
11
- var _Device = _interopRequireDefault(require("./Device"));
20
+ const SCARD_LEAVE_CARD = addon.SCARD_LEAVE_CARD;
21
+ const SCARD_RESET_CARD = addon.SCARD_RESET_CARD;
22
+ const SCARD_UNPOWER_CARD = addon.SCARD_UNPOWER_CARD;
23
+ const SCARD_EJECT_CARD = addon.SCARD_EJECT_CARD;
12
24
 
13
- var _Card = _interopRequireDefault(require("./Card"));
25
+ const SCARD_STATE_UNAWARE = addon.SCARD_STATE_UNAWARE;
26
+ const SCARD_STATE_IGNORE = addon.SCARD_STATE_IGNORE;
27
+ const SCARD_STATE_CHANGED = addon.SCARD_STATE_CHANGED;
28
+ const SCARD_STATE_UNKNOWN = addon.SCARD_STATE_UNKNOWN;
29
+ const SCARD_STATE_UNAVAILABLE = addon.SCARD_STATE_UNAVAILABLE;
30
+ const SCARD_STATE_EMPTY = addon.SCARD_STATE_EMPTY;
31
+ const SCARD_STATE_PRESENT = addon.SCARD_STATE_PRESENT;
32
+ const SCARD_STATE_ATRMATCH = addon.SCARD_STATE_ATRMATCH;
33
+ const SCARD_STATE_EXCLUSIVE = addon.SCARD_STATE_EXCLUSIVE;
34
+ const SCARD_STATE_INUSE = addon.SCARD_STATE_INUSE;
35
+ const SCARD_STATE_MUTE = addon.SCARD_STATE_MUTE;
14
36
 
15
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
37
+ // Import Devices class
38
+ const { Devices } = require('./devices');
39
+
40
+ // Import error classes
41
+ const { PCSCError, CardRemovedError, TimeoutError } = require('./errors');
16
42
 
17
43
  module.exports = {
18
- Iso7816Application: _Iso7816Application["default"],
19
- CommandApdu: _CommandApdu["default"],
20
- ResponseApdu: _ResponseApdu["default"],
21
- Devices: _Devices["default"],
22
- Device: _Device["default"],
23
- Card: _Card["default"]
24
- };
44
+ // Classes
45
+ Context,
46
+ Reader,
47
+ Card,
48
+ Devices,
49
+ ReaderMonitor,
50
+
51
+ // Error classes
52
+ PCSCError,
53
+ CardRemovedError,
54
+ TimeoutError,
55
+
56
+ // Share modes
57
+ SCARD_SHARE_EXCLUSIVE,
58
+ SCARD_SHARE_SHARED,
59
+ SCARD_SHARE_DIRECT,
60
+
61
+ // Protocols
62
+ SCARD_PROTOCOL_T0,
63
+ SCARD_PROTOCOL_T1,
64
+ SCARD_PROTOCOL_RAW,
65
+ SCARD_PROTOCOL_UNDEFINED,
66
+
67
+ // Disposition
68
+ SCARD_LEAVE_CARD,
69
+ SCARD_RESET_CARD,
70
+ SCARD_UNPOWER_CARD,
71
+ SCARD_EJECT_CARD,
72
+
73
+ // State flags
74
+ SCARD_STATE_UNAWARE,
75
+ SCARD_STATE_IGNORE,
76
+ SCARD_STATE_CHANGED,
77
+ SCARD_STATE_UNKNOWN,
78
+ SCARD_STATE_UNAVAILABLE,
79
+ SCARD_STATE_EMPTY,
80
+ SCARD_STATE_PRESENT,
81
+ SCARD_STATE_ATRMATCH,
82
+ SCARD_STATE_EXCLUSIVE,
83
+ SCARD_STATE_INUSE,
84
+ SCARD_STATE_MUTE,
85
+ };