npm package: @kodeglot/node-python-eid-reader
Description: A cross-platform Node.js package to read Belgian eID card public data using Python eidreader with automatic dependency checking
Author: Kodeglot
Latest version: 1.0.1

# Belgian eID Reader

[![npm version](https://img.shields.io/npm/v/@kodeglot/node-python-eid-reader.svg?logo=npm)](https://www.npmjs.com/package/@kodeglot/node-python-eid-reader)

A cross-platform Node.js package to read Belgian eID card public data using Python eidreader with automatic dependency checking. Perfect for Electron apps and Node.js applications.

## Features

- 🔍 **Cross-platform support**: macOS, Windows, and Linux
- 🐍 **Python integration**: Uses the reliable eidreader Python package
- 🔧 **Automatic setup**: Checks and installs missing dependencies
- 🛡️ **Error handling**: Comprehensive error handling with specific error codes
- ⚡ **Configurable**: Customizable retry logic and paths
- 📦 **npm package**: Easy to install and use in any Node.js project
- 🖥️ **Electron ready**: Works seamlessly in Electron applications
- 🔄 **Real-time detection**: Instant PC/SC reader and card insertion/removal detection
- 📡 **Event-driven**: Listen for reader and card events in real-time
- 🎯 **Dual API**: Both traditional eID reading and modern PC/SC detection

## Installation

```bash
npm install @kodeglot/node-python-eid-reader
```

## Prerequisites

Before using this package, you need to install:

1. **Python 3.x** - [Download from python.org](https://www.python.org/downloads/)
2. **Belgian eID middleware** - [Download from Belgian government](https://eid.belgium.be/en/using-your-eid/installing-eid-software)
3. **eidreader Python package** - Will be installed automatically

## Quick Start

### Basic Usage

```javascript
import { readEidData } from '@kodeglot/node-python-eid-reader';

try {
  const eidData = await readEidData();
  console.log('Card holder:', eidData.firstName, eidData.name);
  console.log('Card number:', eidData.cardNumber);
} catch (error) {
  console.error('Failed to read eID:', error.message);
}
```

### Advanced Usage with Options

```javascript
import { EidReader } from '@kodeglot/node-python-eid-reader';

const reader = new EidReader({
  verbose: true,           // Enable detailed logging
  maxRetries: 5,          // Custom retry attempts
  retryDelay: 500,        // Custom retry delay (ms)
  pythonPath: '/usr/bin/python3',  // Custom Python path
  pkcs11LibPath: '/custom/path/libbeidpkcs11.dylib'  // Custom PKCS#11 library
});

try {
  const eidData = await reader.readEidData();
  console.log('Success:', eidData);
} catch (error) {
  console.error('Error:', error.message, error.code);
}
```

### Requirements Management

```javascript
import { checkRequirements, installRequirements } from '@kodeglot/node-python-eid-reader';

// Check if all requirements are met
const result = await checkRequirements();
// NOTE: In Electron/Node.js integration, check result.data.passed, not result.passed
if (result.data && result.data.passed) {
  console.log('All requirements are met!');
} else {
  console.log('Missing requirements:', result.data?.results);
  // Attempt to install missing requirements
  const installResult = await installRequirements();
  if (installResult.success) {
    if (installResult.data && installResult.data.installed) {
      console.log('Requirements installed successfully!');
    } else {
      console.log('All requirements are already available!');
    }
  } else {
    console.log('Failed to install requirements:', installResult.info);
    if (installResult.error) {
      console.log('Error:', installResult.error.message);
    }
  }
}
```

## API Reference

### Real-Time PC/SC Detection (New!)

The package now includes **real-time smart card reader and card detection** using the cross-platform PC/SC subsystem. This provides instant detection of reader insertion/removal and card insertion/removal events.

#### Quick Start - Real-Time Detection

```javascript
import { createPcscDetector } from '@kodeglot/node-python-eid-reader';

// Create a detector for real-time monitoring
const detector = await createPcscDetector();

// Listen for reader events
detector.on('reader-connected', (reader) => {
  console.log('Reader connected:', reader.name);
});

detector.on('reader-removed', (reader) => {
  console.log('Reader removed:', reader.name);
});

// Listen for card events
detector.on('card-inserted', (reader) => {
  console.log('Card inserted in:', reader.name);
});

detector.on('card-removed', (reader) => {
  console.log('Card removed from:', reader.name);
});

// Get current status
const status = detector.getStatus();
console.log('Current readers:', status.readers);

// Clean up when done
detector.close();
```

#### Convenience Functions

```javascript
import { 
  isPcscReaderAvailable, 
  isPcscCardInserted, 
  getPcscStatus 
} from '@kodeglot/node-python-eid-reader';

// Check if any reader is available
const readerResult = await isPcscReaderAvailable();
if (readerResult.success && readerResult.data?.available) {
  console.log('PC/SC reader is available');
}

// Check if any card is inserted
const cardResult = await isPcscCardInserted();
if (cardResult.success && cardResult.data?.inserted) {
  console.log('Card is inserted in:', cardResult.data.readerName);
}

// Get detailed status
const statusResult = await getPcscStatus();
if (statusResult.success) {
  console.log('All readers:', statusResult.data?.readers);
}
```

#### PcscDetector Class

The main class for real-time PC/SC monitoring.

```javascript
import { PcscDetector } from '@kodeglot/node-python-eid-reader';

const detector = new PcscDetector();

// Events
detector.on('reader-connected', (reader: PcscReaderStatus) => {
  // Reader was connected
});

detector.on('reader-removed', (reader: PcscReaderStatus) => {
  // Reader was removed
});

detector.on('card-inserted', (reader: { name: string }) => {
  // Card was inserted
});

detector.on('card-removed', (reader: { name: string }) => {
  // Card was removed
});

// Methods
const status = detector.getStatus(); // Get current status
detector.close(); // Clean up resources
```

#### Types

```typescript
interface PcscReaderStatus {
  name: string;        // Reader name/identifier
  cardPresent: boolean; // Whether a card is currently inserted
}

interface PcscStatus {
  readers: PcscReaderStatus[]; // List of all connected readers
}
```

### Traditional eID Reading API

### `readEidData(options?)`

Convenience function to read eID data with default options.

**Parameters:**
- `options` (optional): `EidReaderOptions` - Configuration options

**Returns:** `Promise<EidData>` - The eID card data

### `EidReader` Class

Main class for reading eID data with full control over options.

#### Constructor

```javascript
new EidReader(options?: EidReaderOptions)
```

**Options:**
- `verbose?: boolean` - Enable verbose logging (default: false)
- `maxRetries?: number` - Maximum retry attempts (default: 3)
- `retryDelay?: number` - Delay between retries in ms (default: 300)
- `pythonPath?: string` - Custom Python interpreter path
- `pkcs11LibPath?: string` - Custom PKCS#11 library path

#### Methods

- `readEidData(): Promise<EidData>` - Read eID data from the card
- `checkRequirements(): Promise<{passed: boolean, results: any}>` - Check if requirements are met
- `installRequirements(): Promise<ReaderResult<{installed: boolean}>>` - Install missing requirements

### `EidData` Interface

```typescript
interface EidData {
  cardNumber: string;           // Card number (unique identifier)
  chipNumber: string;           // Chip number
  validityDateBegin: string;    // Card validity start date
  validityDateEnd: string;      // Card validity end date
  municipality: string;         // Issuing municipality
  nationality: string;          // Nationality
  birthLocation: string;        // Birth location
  birthDate: string;            // Birth date
  name: string;                 // Last name
  firstName: string;            // First name(s)
  sex: string;                  // Gender
  documentType: string;         // Document type
  address: {
    streetAndNumber: string;    // Street and number
    zip: string;                // ZIP code
    municipality: string;       // Municipality
  };
  photo: string;                // Base64 encoded photo
}
```

### `EidReaderError` Class

Custom error class with error codes for better error handling.

**Error Codes:**
- `REQUIREMENTS_NOT_MET` - Missing dependencies
- `PYTHON_NOT_FOUND` - Python interpreter not found
- `EIDREADER_FAILED` - Python eidreader script failed
- `SPAWN_ERROR` - Failed to start Python process
- `MAX_RETRIES_EXCEEDED` - Max retry attempts exceeded
- `INVALID_OUTPUT` - Invalid output from eidreader
- `PARSE_ERROR` - Failed to parse eID data

## Electron Integration

This package works seamlessly in Electron applications with built-in enable/disable functionality and **real-time PC/SC detection**. Here's an example:

### Real-Time Detection in Electron

```javascript
// main.js (Electron main process)
import { createPcscDetector } from '@kodeglot/node-python-eid-reader';

let pcscDetector = null;
let eidReaderEnabled = true;

// Initialize PC/SC detection
async function initializePcscDetection() {
  if (eidReaderEnabled) {
    pcscDetector = await createPcscDetector();
    
    pcscDetector.on('reader-connected', (reader) => {
      mainWindow.webContents.send('reader-state-changed', {
        readerAvailable: true,
        cardInserted: false,
        cardInfo: null
      });
    });
    
    pcscDetector.on('reader-removed', () => {
      mainWindow.webContents.send('reader-state-changed', {
        readerAvailable: false,
        cardInserted: false,
        cardInfo: null
      });
    });
    
    pcscDetector.on('card-inserted', (reader) => {
      mainWindow.webContents.send('reader-state-changed', {
        readerAvailable: true,
        cardInserted: true,
        cardInfo: null
      });
    });
    
    pcscDetector.on('card-removed', () => {
      mainWindow.webContents.send('reader-state-changed', {
        readerAvailable: true,
        cardInserted: false,
        cardInfo: null
      });
    });
  }
}

// Handle enable/disable eID reader
ipcMain.handle('set-eid-enabled', async (event, enabled) => {
  eidReaderEnabled = enabled;
  
  if (enabled) {
    await initializePcscDetection();
  } else {
    if (pcscDetector) {
      pcscDetector.close();
      pcscDetector = null;
    }
  }
  
  return { success: true, enabled: eidReaderEnabled };
});

// Get current PC/SC status
ipcMain.handle('get-pcsc-status', async () => {
  if (!pcscDetector) {
    return { success: false, error: 'PC/SC detector not initialized' };
  }
  
  const status = pcscDetector.getStatus();
  const readerAvailable = status.readers.length > 0;
  const cardInserted = status.readers.some(reader => reader.cardPresent);
  
  return {
    success: true,
    data: {
      readerAvailable,
      cardInserted,
      readers: status.readers
    }
  };
});
```

```javascript
// renderer.js (Electron renderer process)
const { ipcRenderer } = require('electron');

// Listen for real-time reader/card state changes
ipcRenderer.on('reader-state-changed', (event, data) => {
  console.log('Reader state changed:', data);
  updateUI(data.readerAvailable, data.cardInserted);
});

// Get initial status
async function getInitialStatus() {
  try {
    const result = await ipcRenderer.invoke('get-pcsc-status');
    if (result.success) {
      updateUI(result.data.readerAvailable, result.data.cardInserted);
    }
  } catch (error) {
    console.error('Failed to get PC/SC status:', error);
  }
}

function updateUI(readerAvailable, cardInserted) {
  // Update your UI based on reader and card status
  document.getElementById('reader-status').textContent = 
    readerAvailable ? 'Available' : 'Not Available';
  document.getElementById('card-status').textContent = 
    cardInserted ? 'Inserted' : 'Not Inserted';
}
```

### Traditional eID Reading in Electron

This package works seamlessly in Electron applications with built-in enable/disable functionality. Here's an example:

```javascript
// main.js (Electron main process)
import { readEidData } from '@kodeglot/node-python-eid-reader';

let eidReaderEnabled = true; // Default to enabled

// Handle eID reading with enable/disable check
ipcMain.handle('read-eid', async (event) => {
  if (!eidReaderEnabled) {
    return { success: false, error: 'eID reader is disabled', code: 'DISABLED' };
  }
  
  try {
    const eidData = await readEidData({ verbose: true });
    return { success: true, data: eidData };
  } catch (error) {
    return { success: false, error: error.message, code: error.code };
  }
});

// Handle enable/disable eID reader
ipcMain.handle('set-eid-enabled', async (event, enabled) => {
  eidReaderEnabled = enabled;
  return { success: true, enabled: eidReaderEnabled };
});

// Handle get eID reader status
ipcMain.handle('get-eid-status', async () => {
  return { success: true, enabled: eidReaderEnabled };
});
```

```javascript
// renderer.js (Electron renderer process)
const { ipcRenderer } = require('electron');

async function readEID() {
  try {
    const result = await ipcRenderer.invoke('read-eid');
    if (result.success) {
      console.log('eID data:', result.data);
    } else if (result.code === 'DISABLED') {
      console.log('eID reader is disabled');
    } else {
      console.error('Failed:', result.error);
    }
  } catch (error) {
    console.error('IPC error:', error);
  }
}

async function toggleEidReader(enabled) {
  try {
    const result = await ipcRenderer.invoke('set-eid-enabled', enabled);
    if (result.success) {
      console.log(`eID reader ${enabled ? 'enabled' : 'disabled'}`);
    }
  } catch (error) {
    console.error('Toggle error:', error);
  }
}

async function getEidStatus() {
  try {
    const result = await ipcRenderer.invoke('get-eid-status');
    if (result.success) {
      console.log(`eID reader is ${result.enabled ? 'enabled' : 'disabled'}`);
    }
  } catch (error) {
    console.error('Status error:', error);
  }
}
```

### Enable/Disable Features

The Electron integration includes:

- **Toggle Switch**: Visual toggle to enable/disable the eID reader
- **Status Indicator**: Shows current enabled/disabled state
- **Automatic Blocking**: Reading operations are blocked when disabled
- **State Persistence**: Status is maintained during the app session
- **Error Handling**: Proper error codes for disabled state

### Robust Requirements Check in Electron UI

- The requirements check result is an object with a `data` property.
- To check if all requirements are met, use `result.data.passed` in your UI code.
- Example:

```js
if (result.success) {
  if (result.data && result.data.passed) {
    // All requirements are met
  } else {
    // Some requirements are missing
  }
}
```

- This ensures your UI accurately reflects the backend requirements check result.

### Improved Requirements Installation

The `installRequirements()` function now provides better feedback:

- **All requirements available**: Returns success with "All requirements are already available!"
- **Requirements installed**: Returns success with "Requirements installed successfully!"
- **Installation failed**: Returns failure with detailed info and error messages

Example handling in your UI:

```js
const result = await installRequirements();
if (result.success) {
  if (result.data && result.data.installed) {
    console.log('Requirements installed successfully!');
  } else {
    console.log('All requirements are already available!');
  }
} else {
  console.log('Failed to install requirements:', result.info);
  if (result.error) {
    console.log('Error:', result.error.message);
  }
}
```

## Examples

The package includes TypeScript examples demonstrating different usage patterns:

### Node.js Example
```bash
npm run example:node
```

### Electron Example
```bash
npm run example:electron
```

The examples are written in TypeScript and show:
- Basic usage with the convenience function
- Advanced usage with the `EidReader` class
- Error handling and requirements management
- Electron integration with IPC handlers



## Troubleshooting

### Common Issues

1. **"Python not found"**
   - Ensure Python 3.x is installed and in your PATH
   - Use the `pythonPath` option to specify a custom Python path

2. **"eidreader not found"**
   - The package will attempt to install it automatically
   - Manual installation: `pip install eidreader`

3. **"Middleware not properly installed"**
   - Install Belgian eID middleware from [eid.belgium.be](https://eid.belgium.be)
   - On macOS, ensure the middleware is in `/Library/Belgium Identity Card/`
   - On Windows, ensure the middleware is in `C:\Program Files\Belgium Identity Card\` or `C:\Program Files (x86)\Belgium Identity Card\`

4. **"Device error"**
   - Ensure the eID card is properly inserted
   - Try removing and reinserting the card
   - Check if the card reader is working

5. **"Permission denied"**
   - On macOS/Linux, you may need to run with sudo for middleware access
   - The package will prompt for password when needed

### Debug Mode

Enable verbose logging to see detailed information:

```javascript
const reader = new EidReader({ verbose: true });
const eidData = await reader.readEidData();
```

### Environment Variables

You can set these environment variables to customize behavior:

- `PYKCS11LIB` - Path to PKCS#11 library
- `DYLD_LIBRARY_PATH` - Library path (macOS)
- `PYTHONPATH` - Python module search path

## Platform-Specific Notes

### macOS
- Middleware typically installed in `/Library/Belgium Identity Card/`
- May require sudo for first-time access
- DYLD_LIBRARY_PATH is automatically configured

### Windows
- Middleware typically installed in `C:\Program Files\Belgium Identity Card\`
- Ensure the middleware is properly registered
- May require running as administrator

### Linux
- Middleware typically installed in `/usr/lib/`
- May need to install additional packages: `sudo apt-get install libbeidpkcs11-0`

## Development

```bash
# Clone the repository
git clone https://github.com/Kodeglot/Node-Python-EID-Reader.git
cd Node-Python-EID-Reader

# Install dependencies
npm install

# Build the package
npm run build

# Run tests
npm start

# Check requirements
npm run check

# Run examples
npm run example:node
npm run example:electron
```

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

If you encounter any issues:

1. Check the [troubleshooting section](#troubleshooting)
2. Search existing [issues](https://github.com/Kodeglot/Node-Python-EID-Reader/issues)
3. Create a new issue with detailed information about your problem

## Changelog

### 1.0.0
- Initial release
- Cross-platform support
- Automatic dependency checking
- Electron integration
- Comprehensive error handling