UNPKG

emv

Version:

EMV / Chip and PIN CLI and library for PC/SC card readers

139 lines 6.82 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useCallback } from 'react'; import { Box, Text, useInput } from 'ink'; import SelectInput from 'ink-select-input'; import { format as formatTlv, findTagInBuffer, formatGpoResponse } from '../../emv-tags.js'; import { Header, Footer, CardBox, StatusBar, LoadingSpinner } from '../components/index.js'; export function ExploreScreen({ emv, app, onBack }) { const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [error, setError] = useState(null); const [selectedAction, setSelectedAction] = useState(null); useInput((_input, key) => { if (key.escape) { if (selectedAction) { setSelectedAction(null); } else { onBack(); } } }); const fetchData = useCallback(async (action) => { setLoading(true); setError(null); setData([]); try { switch (action) { case 'gpo': { const response = await emv.getProcessingOptions(); if (response.isOk()) { const formatted = formatGpoResponse(response.buffer); setData([ { tag: 'GPO', name: 'GET_PROCESSING_OPTIONS', value: formatted }, ]); } else { setError(`GET PROCESSING OPTIONS failed: SW=${response.sw1.toString(16)}${response.sw2.toString(16)}`); } break; } case 'records': { const records = []; for (let sfi = 1; sfi <= 10; sfi++) { for (let rec = 1; rec <= 10; rec++) { const response = await emv.readRecord(sfi, rec); if (response.isOk()) { const formatted = formatTlv(response); records.push({ tag: `SFI${String(sfi)}:R${String(rec)}`, name: 'RECORD', value: formatted || response.buffer.toString('hex'), }); } else if (response.sw1 === 0x6a && response.sw2 === 0x83) { // Record not found, try next SFI break; } } } if (records.length === 0) { setError('No records found'); } else { setData(records); } break; } case 'pincount': { const response = await emv.getData(0x9f17); if (response.isOk()) { const tagValue = findTagInBuffer(response.buffer, 0x9f17); const count = tagValue?.[0]; setData([ { tag: '9F17', name: 'PIN_TRY_COUNT', value: count !== undefined ? String(count) : 'Unknown', }, ]); } else { setError(`PIN try count not available: SW=${response.sw1.toString(16)}${response.sw2.toString(16)}`); } break; } case 'atc': { const response = await emv.getData(0x9f36); if (response.isOk()) { const tagValue = findTagInBuffer(response.buffer, 0x9f36); const atcValue = tagValue && tagValue.length >= 2 ? tagValue.readUInt16BE(0) : undefined; setData([ { tag: '9F36', name: 'APP_TRANSACTION_COUNTER', value: atcValue !== undefined ? String(atcValue) : 'Unknown', }, ]); } else { setError(`ATC not available: SW=${response.sw1.toString(16)}${response.sw2.toString(16)}`); } break; } } } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, [emv]); const items = [ { label: '📊 Get Processing Options', value: 'gpo' }, { label: '📁 Read All Records', value: 'records' }, { label: '🔢 PIN Try Counter', value: 'pincount' }, { label: '🔄 Application Transaction Counter', value: 'atc' }, { label: '← Back', value: 'back' }, ]; if (loading) { return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), _jsx(LoadingSpinner, { message: "Reading card data..." })] })); } return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), _jsxs(CardBox, { title: `Exploring: ${app.label ?? app.aid}`, children: [error && _jsx(StatusBar, { message: error, type: "error" }), data.length > 0 && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: data.map((item, i) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [item.tag, " (", item.name, "):"] }), _jsx(Text, { color: "yellow", wrap: "wrap", children: item.value })] }, i))) })), _jsx(SelectInput, { items: items, onSelect: (item) => { if (item.value === 'back') { onBack(); } else { setSelectedAction(item.value); void fetchData(item.value); } }, itemComponent: ({ isSelected, label }) => isSelected ? (_jsxs(Text, { color: "cyan", bold: true, children: ['▸ ', label] })) : (_jsxs(Text, { children: [' ', label] })) })] }), _jsx(Footer, { hints: [ { keys: '↑↓', description: 'Navigate' }, { keys: 'Enter', description: 'Select' }, { keys: 'Esc', description: 'Back' }, ] })] })); } //# sourceMappingURL=ExploreScreen.js.map