@kodeglot/node-python-eid-reader
Version:
A cross-platform Node.js package to read Belgian eID card public data using Python eidreader with automatic dependency checking
699 lines • 32.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.EidReader = exports.EidReaderError = void 0;
exports.readEidData = readEidData;
exports.checkRequirements = checkRequirements;
exports.installRequirements = installRequirements;
exports.checkEidReaderAvailable = checkEidReaderAvailable;
exports.checkCardInserted = checkCardInserted;
exports.createPcscDetector = createPcscDetector;
exports.getPcscStatus = getPcscStatus;
exports.isPcscReaderAvailable = isPcscReaderAvailable;
exports.isPcscCardInserted = isPcscCardInserted;
const child_process_1 = require("child_process");
const os = __importStar(require("os"));
const requirements_checker_1 = require("./requirements-checker");
const pcsc_detect_1 = require("./pcsc-detect");
class EidReaderError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = 'EidReaderError';
}
}
exports.EidReaderError = EidReaderError;
class EidReader {
constructor(options = {}) {
this.info = [];
this.options = {
verbose: options.verbose ?? false,
maxRetries: options.maxRetries ?? 3,
retryDelay: options.retryDelay ?? 300,
pythonPath: options.pythonPath ?? '',
pkcs11LibPath: options.pkcs11LibPath ?? ''
};
this.requirementsChecker = new requirements_checker_1.RequirementsChecker();
}
addInfo(msg) {
this.info.push(msg);
if (this.options.verbose) {
// Optionally log for debugging
// console.log(msg);
}
}
async checkRequirements() {
this.info = [];
try {
this.addInfo('Checking system requirements...');
const result = await this.requirementsChecker.checkAllRequirements();
if (result.passed) {
this.addInfo('All requirements are met!');
}
else {
this.addInfo('Some requirements are missing.');
}
return { success: result.passed, info: [...this.info], data: result };
}
catch (error) {
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
async installRequirements() {
this.info = [];
try {
this.addInfo('Checking for missing requirements...');
const { results, passed } = await this.requirementsChecker.checkAllRequirements();
if (passed) {
this.addInfo('All requirements are already available!');
return { success: true, info: [...this.info], data: { installed: true } };
}
const installed = await this.requirementsChecker.installMissingRequirements(results);
if (installed) {
this.addInfo('Requirements installed successfully!');
}
else {
this.addInfo('Some requirements could not be installed automatically.');
}
return { success: installed, info: [...this.info], data: { installed } };
}
catch (error) {
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
async checkEidReaderAvailable() {
this.info = [];
try {
this.addInfo('Checking eID reader availability...');
// First check if requirements are met
const reqResult = await this.checkRequirements();
if (!reqResult.success) {
this.addInfo('Requirements not met, eID reader not available');
return { success: false, info: [...this.info], error: reqResult.error };
}
// Try to find Python with eidreader
let pythonCmd;
try {
pythonCmd = await this.findPythonWithEidreader();
this.addInfo(`Python found: ${pythonCmd}`);
}
catch (error) {
this.addInfo('Python with eidreader not found');
return { success: false, info: [...this.info], error: { message: error.message } };
}
// Try to actually read from a reader to check if one is connected
try {
const env = this.setupEnvironment();
this.addInfo('Environment configured successfully');
// Try to read from a reader with a short timeout
const testResult = await new Promise((resolve) => {
const testProc = (0, child_process_1.spawn)(pythonCmd, ['-m', 'eidreader'], {
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
let stdout = '';
let stderr = '';
testProc.stdout.on('data', (data) => { stdout += data.toString(); });
testProc.stderr.on('data', (data) => { stderr += data.toString(); });
testProc.on('close', (code) => {
// LOGGING: Print raw output for debugging
if (this.options.verbose || process.env.EIDREADER_DEBUG) {
console.log('[checkEidReaderAvailable] process closed with code:', code);
console.log('[checkEidReaderAvailable] stdout:', stdout);
console.log('[checkEidReaderAvailable] stderr:', stderr);
}
// If we get valid JSON output, parse it to check the success field
if (stdout.trim() && stdout.includes('{')) {
try {
const jsonLine = stdout.split('\n').find(line => line.trim().startsWith('{'));
if (jsonLine) {
const data = JSON.parse(jsonLine);
// If success is true, reader is available and card is inserted
if (data.success === true) {
resolve({ available: true, stderr, stdout });
return;
}
// If success is false, check the message to determine if it's reader or card issue
if (data.success === false && data.message) {
const message = data.message.toLowerCase();
// Check for reader-related errors
const readerErrors = [
'no reader',
'no readers',
'no device',
'no smartcard reader',
'no card reader',
'no smart card reader',
'no pc/sc readers',
'no readers available',
'no readers detected',
'no smart card reader detected',
'could not find any reader',
'no reader found',
'no device found',
];
// Check for card-related errors (reader is available but no card)
const cardErrors = [
'ckr_device_error',
'no card',
'card not present',
'no card found',
'card not inserted',
'no smart card inserted',
'no card in reader',
'no card present',
'no card detected',
'could not find any reader with a card inserted',
];
const hasCardError = cardErrors.some(err => message.includes(err));
if (hasCardError) {
resolve({ available: true, stderr, stdout });
return;
}
const hasReaderError = readerErrors.some(err => message.includes(err));
if (hasReaderError) {
resolve({ available: false, stderr, stdout });
return;
}
// If it's not a reader error, assume reader is available but no card
resolve({ available: true, stderr, stdout });
return;
}
// If we can't parse the success field, assume available
resolve({ available: true, stderr, stdout });
return;
}
}
catch { }
}
// Check for known no-reader errors in stderr or stdout
const noReaderErrors = [
'No reader found',
'No device found',
'No smartcard reader found',
'No readers found',
'No reader connected',
'No PC/SC readers',
'No readers available',
'No smart card readers found',
'No card readers found',
'No card reader found',
'No readers detected',
'No smart card reader detected',
];
const hasNoReaderError = noReaderErrors.some(err => stderr.toLowerCase().includes(err.toLowerCase()) || stdout.toLowerCase().includes(err.toLowerCase()));
// If we get a no-reader error, reader is not available
if (hasNoReaderError) {
resolve({ available: false, stderr, stdout });
return;
}
// If we get card-related errors (like "no card"), reader is available
const cardErrors = [
'no card',
'card not present',
'no card found',
'card not inserted',
'no smart card inserted',
'no card in reader',
'no card present',
'no card detected',
];
const hasCardError = cardErrors.some(err => stderr.toLowerCase().includes(err.toLowerCase()) || stdout.toLowerCase().includes(err.toLowerCase()));
if (hasCardError) {
resolve({ available: true, stderr, stdout });
return;
}
// Default to not available if we can't determine
resolve({ available: false, stderr, stdout });
});
testProc.on('error', (error) => {
if (this.options.verbose || process.env.EIDREADER_DEBUG) {
console.log('[checkEidReaderAvailable] process error:', error);
}
resolve({ available: false, stderr: '', stdout: '' });
});
// Timeout after 10 seconds
setTimeout(() => {
testProc.kill();
resolve({ available: false, stderr, stdout });
}, 10000);
});
if (testResult.available) {
this.addInfo('eID reader is available');
return { success: true, info: [...this.info], data: { available: true, readerType: 'hardware' } };
}
else {
this.addInfo('No eID reader detected');
return { success: true, info: [...this.info], data: { available: false } };
}
}
catch (error) {
this.addInfo('Failed to test eID reader');
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
catch (error) {
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
async checkCardInserted() {
this.info = [];
try {
this.addInfo('Checking for inserted eID card...');
// First check if reader is available
const readerResult = await this.checkEidReaderAvailable();
if (!readerResult.success || !readerResult.data?.available) {
this.addInfo('eID reader not available');
return { success: false, info: [...this.info], error: readerResult.error };
}
// Try to read card data with a short timeout
try {
const pythonCmd = await this.findPythonWithEidreader();
const env = this.setupEnvironment();
const cardResult = await new Promise((resolve) => {
const cardProc = (0, child_process_1.spawn)(pythonCmd, ['-m', 'eidreader'], {
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
let stdout = '';
let stderr = '';
let hasOutput = false;
cardProc.stdout.on('data', (data) => {
stdout += data.toString();
hasOutput = true;
});
cardProc.stderr.on('data', (data) => {
stderr += data.toString();
hasOutput = true;
});
cardProc.on('close', (code) => {
// LOGGING: Print raw output for debugging
if (this.options.verbose || process.env.EIDREADER_DEBUG) {
console.log('[checkCardInserted] process closed with code:', code);
console.log('[checkCardInserted] stdout:', stdout);
console.log('[checkCardInserted] stderr:', stderr);
}
// If we get valid JSON output, parse it to check the success field
if (stdout.trim() && stdout.includes('{')) {
try {
const jsonLine = stdout.split('\n').find(line => line.trim().startsWith('{'));
if (jsonLine) {
const data = JSON.parse(jsonLine);
// If success is true, card is inserted
if (data.success === true) {
resolve({ inserted: true, data, stderr, stdout });
return;
}
// If success is false, card is not inserted
if (data.success === false) {
resolve({ inserted: false, stderr, stdout });
return;
}
// If we can't determine, assume not inserted
resolve({ inserted: false, stderr, stdout });
return;
}
}
catch { }
}
// Check for known no-card errors
const noCardErrors = [
'CKR_DEVICE_ERROR',
'No card found',
'Card not present',
'No reader found',
'No device found',
'No card inserted',
'No smart card inserted',
'No card in reader',
'No card present',
'No card detected',
];
const hasNoCardError = noCardErrors.some(err => stderr.includes(err) || stdout.includes(err));
if (hasNoCardError) {
resolve({ inserted: false, stderr, stdout });
return;
}
resolve({ inserted: false, stderr, stdout });
});
cardProc.on('error', (error) => {
if (this.options.verbose || process.env.EIDREADER_DEBUG) {
console.log('[checkCardInserted] process error:', error);
}
resolve({ inserted: false, stderr: '', stdout: '' });
});
// Timeout after 10 seconds
setTimeout(() => {
cardProc.kill();
resolve({ inserted: false, stderr, stdout });
}, 10000);
});
if (cardResult.inserted && cardResult.data) {
this.addInfo('eID card detected and readable');
return {
success: true,
info: [...this.info],
data: {
inserted: true,
cardInfo: {
cardNumber: cardResult.data.card_number,
name: `${cardResult.data.firstnames || ''} ${cardResult.data.surname || ''}`.trim()
}
}
};
}
else {
this.addInfo('No eID card detected');
return { success: true, info: [...this.info], data: { inserted: false } };
}
}
catch (error) {
this.addInfo('Failed to check for card');
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
catch (error) {
return { success: false, info: [...this.info], error: { message: error.message } };
}
}
async readEidData() {
this.info = [];
try {
this.addInfo('Checking requirements before reading eID data...');
const reqResult = await this.checkRequirements();
if (!reqResult.success) {
return { success: false, info: [...this.info, ...reqResult.info], error: reqResult.error };
}
this.addInfo('Requirements OK. Starting eID read...');
const pythonCmd = await this.findPythonWithEidreader();
this.addInfo(`Using Python: ${pythonCmd}`);
const env = this.setupEnvironment();
this.addInfo(`Environment configured for ${os.platform()}`);
const output = await this.runEidReader(pythonCmd, env);
const eidData = this.parseEidreaderOutput(output);
this.addInfo('Successfully read and parsed eID data.');
return { success: true, info: [...this.info], data: eidData };
}
catch (error) {
const err = error instanceof EidReaderError
? { message: error.message, code: error.code }
: { message: error.message };
return { success: false, info: [...this.info], error: err };
}
}
async findPythonWithEidreader() {
if (this.options.pythonPath) {
const isValid = await this.tryPythonCommand(this.options.pythonPath);
if (isValid)
return this.options.pythonPath;
throw new EidReaderError(`Invalid Python path: ${this.options.pythonPath}`);
}
const candidates = os.platform() === 'win32'
? ['python', 'python3']
: ['/usr/bin/python3', '/opt/homebrew/bin/python3', 'python3', 'python'];
for (const cmd of candidates) {
const found = await this.tryPythonCommand(cmd);
if (found)
return found;
}
throw new EidReaderError('Could not find a Python interpreter with eidreader installed. Please install Python 3.x and eidreader package.', 'PYTHON_NOT_FOUND');
}
async tryPythonCommand(pythonCmd) {
return new Promise((resolve) => {
const proc = (0, child_process_1.spawn)(pythonCmd, ['-m', 'eidreader', '--help']);
let output = '';
let error = '';
proc.stdout.on('data', (data) => (output += data.toString()));
proc.stderr.on('data', (data) => (error += data.toString()));
proc.on('close', (code) => {
if (code === 0 && output.includes('eidreader')) {
resolve(pythonCmd);
}
else {
resolve(null);
}
});
proc.on('error', () => resolve(null));
});
}
setupEnvironment() {
const env = { ...process.env };
if (os.platform() === 'darwin') {
const libraryPaths = [
'/Library/Belgium Identity Card/Pkcs11',
'/Library/Belgium Identity Card/Pkcs11/beid-pkcs11.bundle/Contents/MacOS',
'/usr/local/lib',
'/usr/lib'
];
const existingPath = env.DYLD_LIBRARY_PATH || '';
env.DYLD_LIBRARY_PATH = [...libraryPaths, existingPath].filter(Boolean).join(':');
}
if (this.options.pkcs11LibPath) {
env.PYKCS11LIB = this.options.pkcs11LibPath;
}
else if (process.env.PYKCS11LIB) {
env.PYKCS11LIB = process.env.PYKCS11LIB;
}
else if (os.platform() === 'darwin') {
env.PYKCS11LIB = '/Library/Belgium Identity Card/Pkcs11/libbeidpkcs11.dylib';
}
else if (os.platform() === 'win32') {
env.PYKCS11LIB = 'C:\\Program Files\\Belgium Identity Card\\pkcs11\\beidpkcs11.dll';
}
else if (os.platform() === 'linux') {
env.PYKCS11LIB = 'libbeidpkcs11.so.0';
}
return env;
}
async runEidReader(pythonCmd, env) {
for (let attempt = 1; attempt <= this.options.maxRetries; attempt++) {
try {
this.addInfo(`Running eID reader (attempt ${attempt}/${this.options.maxRetries})...`);
const result = await new Promise((resolve, reject) => {
const eidReader = (0, child_process_1.spawn)(pythonCmd, ['-m', 'eidreader'], {
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
let stdout = '';
let stderr = '';
eidReader.stdout.on('data', (data) => {
stdout += data.toString();
});
eidReader.stderr.on('data', (data) => {
stderr += data.toString();
});
eidReader.on('close', (code) => {
if (code === 0) {
resolve(stdout);
}
else {
reject(new EidReaderError(`eidreader failed with code ${code}: ${stderr}`, 'EIDREADER_FAILED'));
}
});
eidReader.on('error', (error) => {
reject(new EidReaderError(`Failed to start eidreader: ${error.message}`, 'SPAWN_ERROR'));
});
});
return result;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('CKR_DEVICE_ERROR') && attempt < this.options.maxRetries) {
this.addInfo(`Device error detected, retrying in ${this.options.retryDelay}ms...`);
await new Promise(resolve => setTimeout(resolve, this.options.retryDelay));
continue;
}
throw error;
}
}
throw new EidReaderError('Max retries exceeded', 'MAX_RETRIES_EXCEEDED');
}
parseEidreaderOutput(output) {
const jsonLine = output.split('\n').find(line => line.trim().startsWith('{'));
if (!jsonLine) {
throw new EidReaderError('No JSON output from eidreader', 'INVALID_OUTPUT');
}
try {
const data = JSON.parse(jsonLine);
return {
cardNumber: data.card_number || "",
chipNumber: data.chip_number || "",
validityDateBegin: data.validity_begin_date || "",
validityDateEnd: data.validity_end_date || "",
municipality: data.issuing_municipality || "",
nationality: data.nationality || "",
birthLocation: data.location_of_birth || "",
birthDate: data.date_of_birth || "",
name: data.surname || "",
firstName: data.firstnames || "",
sex: data.gender || "",
documentType: data.document_type || "",
address: {
streetAndNumber: data.address_street_and_number || "",
zip: data.address_zip || "",
municipality: data.address_municipality || ""
},
photo: data.PHOTO_FILE || ""
};
}
catch (error) {
throw new EidReaderError(`Failed to parse eID data: ${error instanceof Error ? error.message : String(error)}`, 'PARSE_ERROR');
}
}
}
exports.EidReader = EidReader;
async function readEidData(options) {
const reader = new EidReader(options);
return await reader.readEidData();
}
async function checkRequirements() {
const reader = new EidReader();
return await reader.checkRequirements();
}
async function installRequirements() {
const reader = new EidReader();
return await reader.installRequirements();
}
async function checkEidReaderAvailable() {
const reader = new EidReader();
return await reader.checkEidReaderAvailable();
}
async function checkCardInserted() {
const reader = new EidReader();
return await reader.checkCardInserted();
}
/**
* Create and start a PC/SC detector for real-time reader/card monitoring
* @param options Configuration options
* @returns Promise that resolves to a PcscDetector instance
*/
async function createPcscDetector() {
const detector = new pcsc_detect_1.PcscDetector();
// Wait a moment for initial setup
await new Promise(resolve => setTimeout(resolve, 100));
return detector;
}
/**
* Get current PC/SC status (readers and cards)
* @returns Promise that resolves to current PC/SC status
*/
async function getPcscStatus() {
try {
const detector = new pcsc_detect_1.PcscDetector();
// Wait for initial setup
await new Promise(resolve => setTimeout(resolve, 200));
const status = detector.getStatus();
detector.close();
return {
success: true,
info: ['PC/SC status retrieved successfully'],
data: status
};
}
catch (error) {
return {
success: false,
info: ['Failed to get PC/SC status'],
error: { message: error.message }
};
}
}
/**
* Check if any PC/SC reader is available
* @returns Promise that resolves to reader availability status
*/
async function isPcscReaderAvailable() {
try {
const statusResult = await getPcscStatus();
if (!statusResult.success) {
return {
success: false,
info: statusResult.info,
error: statusResult.error,
data: { available: false }
};
}
const available = statusResult.data.readers.length > 0;
return {
success: true,
info: available ? ['PC/SC reader available'] : ['No PC/SC reader available'],
data: { available }
};
}
catch (error) {
return {
success: false,
info: ['Failed to check PC/SC reader availability'],
error: { message: error.message },
data: { available: false }
};
}
}
/**
* Check if any card is inserted in any PC/SC reader
* @returns Promise that resolves to card insertion status
*/
async function isPcscCardInserted() {
try {
const statusResult = await getPcscStatus();
if (!statusResult.success) {
return {
success: false,
info: statusResult.info,
error: statusResult.error,
data: { inserted: false }
};
}
const cardReader = statusResult.data.readers.find((reader) => reader.cardPresent);
const inserted = !!cardReader;
return {
success: true,
info: inserted ? ['Card inserted'] : ['No card inserted'],
data: {
inserted,
readerName: cardReader?.name
}
};
}
catch (error) {
return {
success: false,
info: ['Failed to check PC/SC card insertion'],
error: { message: error.message },
data: { inserted: false }
};
}
}
//# sourceMappingURL=index.js.map