Files
dt-nfc-identifier/src/hooks/useScan.ts
michael fb7f836abd Add chip detection system for NTAG, DESFire, ISO 15693, and JavaCard
Implement comprehensive NFC chip identification using platform-specific
commands and heuristics:

- NTAG/Ultralight detection via GET_VERSION command
- DESFire EV1/EV2/EV3 and NTAG 424 DNA detection
- ISO 15693 detection with memory-based SLIX/ICODE DNA differentiation
- JavaCard/JCOP detection via CPLC data
- MIFARE Classic detection via SAK values

Use block count as authoritative source for NXP ISO 15693 chips since
IC reference values overlap between SLIX and ICODE DNA families.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 18:08:38 -08:00

202 lines
5.1 KiB
TypeScript

/**
* useScan Hook
* React hook for managing NFC scan state and operations
*/
import {useState, useCallback, useEffect, useRef} from 'react';
import {nfcManager} from '../services/nfc';
import {detectChip} from '../services/detection';
import type {ScanState, RawTagData, ScanError, NFCStatus} from '../types/nfc';
import type {Transponder} from '../types/detection';
export interface UseScanResult {
/** Current scan state */
state: ScanState;
/** Scanned tag data (when state is 'success') */
tag: RawTagData | null;
/** Detected transponder info (when detection succeeds) */
transponder: Transponder | null;
/** Scan error (when state is 'error') */
error: ScanError | null;
/** NFC status (supported and enabled) */
nfcStatus: NFCStatus;
/** Start scanning for a tag */
startScan: () => Promise<void>;
/** Cancel ongoing scan */
cancelScan: () => Promise<void>;
/** Reset state to idle */
reset: () => void;
/** Open device NFC settings */
openSettings: () => Promise<void>;
}
/**
* Hook for managing NFC scanning
*/
export function useScan(): UseScanResult {
const [state, setState] = useState<ScanState>('idle');
const [tag, setTag] = useState<RawTagData | null>(null);
const [transponder, setTransponder] = useState<Transponder | null>(null);
const [error, setError] = useState<ScanError | null>(null);
const [nfcStatus, setNfcStatus] = useState<NFCStatus>({
isSupported: false,
isEnabled: false,
});
// Track if component is mounted to prevent state updates after unmount
const isMounted = useRef(true);
// Track if a scan is in progress
const scanInProgress = useRef(false);
// Initialize NFC and check status
useEffect(() => {
let mounted = true;
async function initialize() {
await nfcManager.init();
if (mounted) {
const status = await nfcManager.getStatus();
setNfcStatus(status);
}
}
initialize();
return () => {
mounted = false;
isMounted.current = false;
// Cleanup on unmount
nfcManager.cancelScan();
};
}, []);
// Start scanning for a tag
const startScan = useCallback(async () => {
// Prevent multiple concurrent scans
if (scanInProgress.current) {
return;
}
scanInProgress.current = true;
setState('scanning');
setTag(null);
setTransponder(null);
setError(null);
try {
// Check NFC status first
const status = await nfcManager.getStatus();
if (isMounted.current) {
setNfcStatus(status);
}
if (!status.isSupported) {
if (isMounted.current) {
setState('error');
setError({
type: 'NFC_NOT_SUPPORTED',
message: 'NFC is not supported on this device',
});
}
return;
}
if (!status.isEnabled) {
if (isMounted.current) {
setState('error');
setError({
type: 'NFC_NOT_ENABLED',
message: 'Please enable NFC in your device settings',
});
}
return;
}
// Perform scan with detection in one session
const result = await nfcManager.scanWithDetection(async tagData => {
// Run chip detection while NFC session is still active
const detectionResult = await detectChip(tagData);
return detectionResult.success ? detectionResult.transponder : null;
});
if (!isMounted.current) {
return;
}
if (result.error) {
// Don't show error for cancellation
if (result.error.type === 'SCAN_CANCELLED') {
setState('idle');
} else {
setState('error');
setError(result.error);
}
} else if (result.tag) {
setState('success');
setTag(result.tag);
if (result.detection) {
setTransponder(result.detection);
}
} else {
setState('error');
setError({
type: 'UNKNOWN',
message: 'No tag data received',
});
}
} catch (err) {
if (isMounted.current) {
setState('error');
setError({
type: 'UNKNOWN',
message: err instanceof Error ? err.message : 'An unknown error occurred',
originalError: err,
});
}
} finally {
scanInProgress.current = false;
}
}, []);
// Cancel ongoing scan
const cancelScan = useCallback(async () => {
await nfcManager.cancelScan();
scanInProgress.current = false;
if (isMounted.current) {
setState('idle');
setError(null);
}
}, []);
// Reset state to idle
const reset = useCallback(() => {
setState('idle');
setTag(null);
setTransponder(null);
setError(null);
}, []);
// Open device NFC settings
const openSettings = useCallback(async () => {
await nfcManager.openNFCSettings();
// Re-check status after returning from settings
const status = await nfcManager.getStatus();
if (isMounted.current) {
setNfcStatus(status);
}
}, []);
return {
state,
tag,
transponder,
error,
nfcStatus,
startScan,
cancelScan,
reset,
openSettings,
};
}