Fixed MFC 1k/4k issue and Ultralight detection

This commit is contained in:
michael
2026-01-26 14:06:49 -08:00
parent b27baf5dec
commit 2409ec3625
10 changed files with 237 additions and 57 deletions

View File

@@ -79,6 +79,40 @@ export const CHIP_INFO: Partial<Record<ChipType, ChipInfo>> = {
memoryNote: '1904 bytes user memory', memoryNote: '1904 bytes user memory',
}, },
// MIFARE Ultralight family
[ChipType.ULTRALIGHT]: {
description:
'Original MIFARE Ultralight - a simple, low-cost NFC tag with minimal memory.',
commonUses: ['Single-use tickets', 'Event passes', 'Disposable tags'],
capabilities: ['NFC Forum Type 2', 'OTP area', 'Lock bits'],
securityLevel: 'low',
memoryNote: '48 bytes user memory',
},
[ChipType.ULTRALIGHT_C]: {
description:
'Ultralight with 3DES authentication for basic security applications.',
commonUses: ['Limited-use tickets', 'Access tokens', 'Loyalty cards'],
capabilities: ['NFC Forum Type 2', '3DES authentication', 'Counter'],
securityLevel: 'medium',
memoryNote: '144 bytes user memory',
},
[ChipType.ULTRALIGHT_EV1]: {
description:
'Enhanced Ultralight with password protection and originality check.',
commonUses: ['Event tickets', 'Brand protection', 'Gaming tokens'],
capabilities: ['Password protection', 'Originality signature', 'Counter'],
securityLevel: 'low',
memoryNote: '48 or 128 bytes user memory',
},
[ChipType.ULTRALIGHT_AES]: {
description:
'Ultralight with AES-128 authentication for secure applications.',
commonUses: ['Secure tickets', 'Brand protection', 'Limited-use passes'],
capabilities: ['AES-128 authentication', 'Counter', 'Originality signature'],
securityLevel: 'high',
memoryNote: '540 bytes user memory',
},
// MIFARE Classic // MIFARE Classic
[ChipType.MIFARE_CLASSIC_1K]: { [ChipType.MIFARE_CLASSIC_1K]: {
description: description:

View File

@@ -64,6 +64,18 @@ const SLIX_COMPATIBLE: ChipType[] = [
ChipType.SLIX_L, ChipType.SLIX_L,
]; ];
/**
* MIFARE Ultralight compatible chips (for Gen4 magic implants)
* Note: Ultralight AES cannot be cloned if AES protection is active
*/
const ULTRALIGHT_COMPATIBLE: ChipType[] = [
ChipType.ULTRALIGHT,
ChipType.ULTRALIGHT_C,
ChipType.ULTRALIGHT_EV1,
ChipType.ULTRALIGHT_NANO,
ChipType.ULTRALIGHT_AES,
];
export const PRODUCTS: Product[] = [ export const PRODUCTS: Product[] = [
// ========================================================================== // ==========================================================================
// X-Series (Glass Capsule) Implants - NFC // X-Series (Glass Capsule) Implants - NFC
@@ -136,8 +148,9 @@ export const PRODUCTS: Product[] = [
'Dual-frequency', 'Dual-frequency',
'NTAG216 NFC chip', 'NTAG216 NFC chip',
'888 bytes user memory', '888 bytes user memory',
'URL/contact sharing',
'T5577 125kHz chip', 'T5577 125kHz chip',
'LF access control', 'Access control (LF)',
], ],
url: 'https://dangerousthings.com/product/next/', url: 'https://dangerousthings.com/product/next/',
canReceiveClone: true, canReceiveClone: true,
@@ -158,7 +171,7 @@ export const PRODUCTS: Product[] = [
'URL/contact sharing', 'URL/contact sharing',
'Green, Blue, and White LEDs available (HF)', 'Green, Blue, and White LEDs available (HF)',
'T5577 125kHz chip', 'T5577 125kHz chip',
'LF access control', 'Access control (LF)',
], ],
url: 'https://dangerousthings.com/product/next-v2/', url: 'https://dangerousthings.com/product/next-v2/',
canReceiveClone: true, canReceiveClone: true,
@@ -175,14 +188,14 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE], compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [ features: [
'Magic MIFARE Classic chip', 'Magic MIFARE Classic chip',
'Changeable UID', 'Changeable UID (4-byte)',
'Gen1a and gen2 magic options',
'T5577 125kHz chip', 'T5577 125kHz chip',
'Clone access cards', 'Access control (LF)',
], ],
url: 'https://dangerousthings.com/product/xmagic/', url: 'https://dangerousthings.com/product/xmagic/',
canReceiveClone: true, canReceiveClone: true,
exactMatch: false, exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)',
}, },
// ========================================================================== // ==========================================================================
@@ -200,7 +213,8 @@ export const PRODUCTS: Product[] = [
'DESFire EV3 chip', 'DESFire EV3 chip',
'8KB memory', '8KB memory',
'Latest security features', 'Latest security features',
'Transaction MAC', 'Access control (HF)',
'URL/contact sharing',
], ],
url: 'https://dangerousthings.com/product/xdf3/', url: 'https://dangerousthings.com/product/xdf3/',
canReceiveClone: false, canReceiveClone: false,
@@ -221,14 +235,12 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE], compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [ features: [
'Magic MIFARE Classic 1K', 'Magic MIFARE Classic 1K',
'Changeable UID', 'Gen1a and gen2 magic options',
'Clone access cards', 'Changeable UID (4-byte)',
'Gen1a compatible',
], ],
url: 'https://dangerousthings.com/product/xm1/', url: 'https://dangerousthings.com/product/xm1/',
canReceiveClone: true, canReceiveClone: true,
exactMatch: false, exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
}, },
// ========================================================================== // ==========================================================================
@@ -243,6 +255,7 @@ export const PRODUCTS: Product[] = [
categories: [ProductCategory.SECURE], categories: [ProductCategory.SECURE],
compatibleChips: [ChipType.NTAG424_DNA, ChipType.NTAG424_DNA_TT, ChipType.ICODE_DNA], compatibleChips: [ChipType.NTAG424_DNA, ChipType.NTAG424_DNA_TT, ChipType.ICODE_DNA],
features: [ features: [
'VivoKey Ecosystem',
'Spark Actions', 'Spark Actions',
'Verify API', 'Verify API',
], ],
@@ -265,9 +278,11 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...NTAG21X_COMPATIBLE, ...NTAG_I2C_COMPATIBLE], compatibleChips: [...NTAG21X_COMPATIBLE, ...NTAG_I2C_COMPATIBLE],
features: [ features: [
'NTAG216 NFC chip', 'NTAG216 NFC chip',
'888 bytes user memory',
'URL/contact sharing',
'T5577 125kHz chip', 'T5577 125kHz chip',
'Enhanced read range',
'Bioresin encapsulation', 'Bioresin encapsulation',
'Enhanced read range',
], ],
url: 'https://dangerousthings.com/product/dnext/', url: 'https://dangerousthings.com/product/dnext/',
canReceiveClone: true, canReceiveClone: true,
@@ -281,17 +296,20 @@ export const PRODUCTS: Product[] = [
'Bioresin dual-frequency implant with Ultimate Gen4 magic MIFARE and T5577 for maximum compatibility.', 'Bioresin dual-frequency implant with Ultimate Gen4 magic MIFARE and T5577 for maximum compatibility.',
formFactor: FormFactor.BIORESIN, formFactor: FormFactor.BIORESIN,
categories: [ProductCategory.DUAL_FREQUENCY, ProductCategory.ACCESS], categories: [ProductCategory.DUAL_FREQUENCY, ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE], compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE, ...ULTRALIGHT_COMPATIBLE],
features: [ features: [
'Ultimate Gen4 magic chip', 'Ultimate Gen4 magic chip',
'Changeable UID',
'Access control (HF)',
'T5577 125kHz chip', 'T5577 125kHz chip',
'Changeable UID', 'Changeable UID',
'Enhanced cloning capability', 'Enhanced cloning capability',
'Access control (LF)',
], ],
url: 'https://dangerousthings.com/product/dug4t/', url: 'https://dangerousthings.com/product/dug4t/',
canReceiveClone: true, canReceiveClone: true,
exactMatch: false, exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)', notes: 'Supports 1K and 4K cloning. Also includes T5577 for LF.',
}, },
// ========================================================================== // ==========================================================================
@@ -308,7 +326,7 @@ export const PRODUCTS: Product[] = [
features: [ features: [
'NTAG216 chip', 'NTAG216 chip',
'888 bytes user memory', '888 bytes user memory',
'Flexible form factor', 'URL/contact sharing',
'Extended read range', 'Extended read range',
], ],
url: 'https://dangerousthings.com/product/flexnt/', url: 'https://dangerousthings.com/product/flexnt/',
@@ -331,7 +349,7 @@ export const PRODUCTS: Product[] = [
'DESFire EV2 chip', 'DESFire EV2 chip',
'8KB memory', '8KB memory',
'AES-128 encryption', 'AES-128 encryption',
'Flexible form factor', 'URL/contact sharing',
], ],
url: 'https://dangerousthings.com/product/flexdf2/', url: 'https://dangerousthings.com/product/flexdf2/',
canReceiveClone: false, canReceiveClone: false,
@@ -343,24 +361,6 @@ export const PRODUCTS: Product[] = [
// ========================================================================== // ==========================================================================
// Flex Implants - Magic/Cloning // Flex Implants - Magic/Cloning
// ========================================================================== // ==========================================================================
{
id: 'flexm1',
name: 'flexM1',
description: 'Flexible magic MIFARE Classic 1K implant for cloning access cards.',
formFactor: FormFactor.FLEX,
categories: [ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [
'Magic MIFARE Classic 1K',
'Changeable UID',
'Flexible form factor',
'Extended read range',
],
url: 'https://dangerousthings.com/product/flexm1/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
},
{ {
id: 'flexm1-v2', id: 'flexm1-v2',
name: 'flexM1 v2', name: 'flexM1 v2',
@@ -377,7 +377,6 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/flexm1-v2/', url: 'https://dangerousthings.com/product/flexm1-v2/',
canReceiveClone: true, canReceiveClone: true,
exactMatch: false, exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
}, },
// ========================================================================== // ==========================================================================
@@ -394,9 +393,9 @@ export const PRODUCTS: Product[] = [
features: [ features: [
'JCOP4 secure element', 'JCOP4 secure element',
'Fidesmo platform', 'Fidesmo platform',
'Payment capable',
'FIDO2/WebAuthn', 'FIDO2/WebAuthn',
'OTP support', 'OTP support',
'URL/contact sharing',
], ],
url: 'https://dangerousthings.com/product/apex-flex/', url: 'https://dangerousthings.com/product/apex-flex/',
canReceiveClone: false, canReceiveClone: false,
@@ -416,6 +415,9 @@ export const PRODUCTS: Product[] = [
'Full JavaCard access', 'Full JavaCard access',
'GlobalPlatformPro compatible', 'GlobalPlatformPro compatible',
'Custom applet support', 'Custom applet support',
'FIDO2/WebAuthn',
'OTP support',
'URL/contact sharing',
], ],
url: 'https://dangerousthings.com/product/flexsecure/', url: 'https://dangerousthings.com/product/flexsecure/',
canReceiveClone: false, canReceiveClone: false,
@@ -436,7 +438,6 @@ export const PRODUCTS: Product[] = [
features: [ features: [
'HID iCLASS SE compatible', 'HID iCLASS SE compatible',
'Enterprise access control', 'Enterprise access control',
'Flexible form factor',
], ],
url: 'https://dangerousthings.com/product/flexclass/', url: 'https://dangerousthings.com/product/flexclass/',
canReceiveClone: false, canReceiveClone: false,
@@ -449,17 +450,16 @@ export const PRODUCTS: Product[] = [
description: 'Flexible Ultimate Gen4 magic MIFARE implant for cloning access cards.', description: 'Flexible Ultimate Gen4 magic MIFARE implant for cloning access cards.',
formFactor: FormFactor.FLEX, formFactor: FormFactor.FLEX,
categories: [ProductCategory.ACCESS], categories: [ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE], compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE, ...ULTRALIGHT_COMPATIBLE],
features: [ features: [
'Ultimate Gen4 magic chip', 'Ultimate Gen4 magic chip',
'Changeable UID', 'Changeable UID',
'Flexible form factor',
'Enhanced cloning capability', 'Enhanced cloning capability',
], ],
url: 'https://dangerousthings.com/product/flexug4/', url: 'https://dangerousthings.com/product/flexug4/',
canReceiveClone: true, canReceiveClone: true,
exactMatch: false, exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)', notes: 'Supports 1K and 4K cloning.',
}, },
]; ];

View File

@@ -3,7 +3,7 @@ import { StyleSheet, View, ScrollView, Linking, TouchableOpacity } from 'react-n
import { Button, Text, Surface, Divider, Chip } from 'react-native-paper'; import { Button, Text, Surface, Divider, Chip } from 'react-native-paper';
import type { ResultScreenProps } from '../types/navigation'; import type { ResultScreenProps } from '../types/navigation';
import { DTColors } from '../theme'; import { DTColors } from '../theme';
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning } from '../services/matching'; import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning, getMifareClassicCapacityWarning } from '../services/matching';
import { Product } from '../types/products'; import { Product } from '../types/products';
import { getChipInfo, getChipFamilyInfo, getSecurityLevelDescription } from '../data/chipInfo'; import { getChipInfo, getChipFamilyInfo, getSecurityLevelDescription } from '../data/chipInfo';
import { getChipFamily } from '../types/detection'; import { getChipFamily } from '../types/detection';
@@ -296,6 +296,13 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
</Text> </Text>
)} )}
{/* MIFARE Classic 4K → 1K capacity warning */}
{getMifareClassicCapacityWarning(transponder.type, product) && (
<Text variant="bodySmall" style={styles.evMismatchWarning}>
{getMifareClassicCapacityWarning(transponder.type, product)}
</Text>
)}
<View style={styles.productFeatures}> <View style={styles.productFeatures}>
{product.features.slice(0, 3).map((feature, idx) => ( {product.features.slice(0, 3).map((feature, idx) => (
<Text key={idx} variant="bodySmall" style={styles.productFeature}> <Text key={idx} variant="bodySmall" style={styles.productFeature}>

View File

@@ -1,13 +1,13 @@
import React, {useCallback, useEffect} from 'react'; import React, { useCallback, useEffect } from 'react';
import {StyleSheet, View, Platform} from 'react-native'; import { StyleSheet, View, Platform } from 'react-native';
import {Button, Text} from 'react-native-paper'; import { Button, Text } from 'react-native-paper';
import type {ScanScreenProps} from '../types/navigation'; import type { ScanScreenProps } from '../types/navigation';
import {DTColors} from '../theme'; import { DTColors } from '../theme';
import {useScan} from '../hooks'; import { useScan } from '../hooks';
import {getScanInstructions} from '../services/nfc'; import { getScanInstructions } from '../services/nfc';
import {ScanAnimation} from '../components'; import { ScanAnimation } from '../components';
export function ScanScreen({navigation}: ScanScreenProps) { export function ScanScreen({ navigation }: ScanScreenProps) {
const { const {
state, state,
tag, tag,
@@ -194,6 +194,12 @@ export function ScanScreen({navigation}: ScanScreenProps) {
<Text variant="bodyLarge" style={styles.instructionText}> <Text variant="bodyLarge" style={styles.instructionText}>
{getScanInstructions()} {getScanInstructions()}
</Text> </Text>
<Text variant="headlineSmall" style={{ paddingTop: 15, paddingBottom: 10 }}>
Not Scanning?
</Text>
<Text variant="bodyLarge" style={styles.instructionText}>
Phones can only read high frequency (13.56 MHz) -- low and ultra high frequency transponders cannot be scanned.
</Text>
{scanProgress && scanProgress.current > 1 && ( {scanProgress && scanProgress.current > 1 && (
<Text variant="bodySmall" style={styles.progressText}> <Text variant="bodySmall" style={styles.progressText}>
{scanProgress.step} {scanProgress.step}

View File

@@ -146,12 +146,29 @@ export async function detectChip(
// If we have MifareClassic tech type and NO IsoDep, it's definitely Classic // If we have MifareClassic tech type and NO IsoDep, it's definitely Classic
if (hasMifareClassicTech && !hasIsoDepTech) { if (hasMifareClassicTech && !hasIsoDepTech) {
// Determine 1K vs 4K based on SAK or UID length // Determine 1K vs 4K vs Mini
// Priority: mifareClassic.size (most reliable on Android) > SAK > UID length
let chipType = ChipType.MIFARE_CLASSIC_1K; // Default to 1K let chipType = ChipType.MIFARE_CLASSIC_1K; // Default to 1K
let memorySize = 1024; let memorySize = 1024;
// SAK 0x18 or 0x38 indicates 4K // First, check mifareClassic object from Android (most reliable)
if (sak === 0x18 || sak === 0x38 || sak === 0x98) { if (rawData.mifareClassic?.size) {
const size = rawData.mifareClassic.size;
console.log('[Detector] Using mifareClassic.size:', size);
if (size >= 4096) {
chipType = ChipType.MIFARE_CLASSIC_4K;
memorySize = 4096;
} else if (size >= 1024) {
chipType = ChipType.MIFARE_CLASSIC_1K;
memorySize = 1024;
} else if (size >= 320) {
chipType = ChipType.MIFARE_CLASSIC_MINI;
memorySize = 320;
}
}
// Fallback to SAK-based detection
// SAK 0x18, 0x38, or 0x98 indicates 4K
else if (sak === 0x18 || sak === 0x38 || sak === 0x98) {
chipType = ChipType.MIFARE_CLASSIC_4K; chipType = ChipType.MIFARE_CLASSIC_4K;
memorySize = 4096; memorySize = 4096;
} }
@@ -233,8 +250,27 @@ export async function detectChip(
}), }),
}; };
} }
// If GET_VERSION failed but tag looks like Type 2, mark as unknown NTAG // If GET_VERSION failed, check for MIFARE Ultralight
if (sak === 0x00 && techTypes.some(t => t.includes('NfcA'))) { // Original Ultralight doesn't support GET_VERSION (only EV1+ does)
// SAK can be 0x00 or undefined for Ultralight
if ((sak === 0x00 || sak === undefined) && techTypes.some(t => t.includes('NfcA'))) {
// Check for MifareUltralight tech type (Android provides this)
const hasMifareUltralightTech = techTypes.some(t =>
t.includes('MifareUltralight'),
);
if (hasMifareUltralightTech) {
console.log('[Detector] MifareUltralight tech detected, identifying as original Ultralight');
return {
success: true,
transponder: createTransponder(ChipType.ULTRALIGHT, rawData, {
memorySize: 48, // Original Ultralight has 48 bytes user memory
confidence: 'medium',
}),
};
}
// No MifareUltralight tech - fall back to unknown NTAG
console.log('[Detector] NTAG detection failed, falling back to NTAG_UNKNOWN'); console.log('[Detector] NTAG detection failed, falling back to NTAG_UNKNOWN');
return { return {
success: true, success: true,

View File

@@ -11,4 +11,5 @@ export {
canCloneToProduct, canCloneToProduct,
getDesfireEvLevel, getDesfireEvLevel,
getDesfireEvMismatchWarning, getDesfireEvMismatchWarning,
getMifareClassicCapacityWarning,
} from './matcher'; } from './matcher';

View File

@@ -35,7 +35,6 @@ export function matchChipToProducts(chip: Transponder): MatchResult {
if (product.canReceiveClone && cloneability?.cloneable) { if (product.canReceiveClone && cloneability?.cloneable) {
if ((product.name.startsWith("xMagic") || product.name.startsWith("xM1") || product.name.startsWith("flexM1")) && chip.rawData.uid.replaceAll(":", "").length / 2 !== 4) { if ((product.name.startsWith("xMagic") || product.name.startsWith("xM1") || product.name.startsWith("flexM1")) && chip.rawData.uid.replaceAll(":", "").length / 2 !== 4) {
// This skips things without a 4-byte UID // This skips things without a 4-byte UID
} else { } else {
cloneTargets.push(product); cloneTargets.push(product);
} }
@@ -177,6 +176,32 @@ export function getDesfireEvLevel(chipType: ChipType): DesfireEvLevel | null {
} }
} }
/**
* IDs of products that only support MIFARE Classic 1K (not 4K)
*/
const MIFARE_1K_ONLY_PRODUCTS = new Set(['xmagic', 'xm1', 'flexm1-v2']);
/**
* Check if a scanned 4K card is being matched to a 1K-only implant
* Returns warning message if capacity mismatch, null if no issue
*/
export function getMifareClassicCapacityWarning(
chipType: ChipType,
product: Product,
): string | null {
// Only applies to MIFARE Classic 4K cards
if (chipType !== ChipType.MIFARE_CLASSIC_4K) {
return null;
}
// Only warn for 1K-only implants
if (!MIFARE_1K_ONLY_PRODUCTS.has(product.id)) {
return null;
}
return 'This implant has 1K memory only - might not have capacity to clone your 4K card.';
}
/** /**
* Check if there's a DESFire EV mismatch between chip and product * Check if there's a DESFire EV mismatch between chip and product
* Returns warning message if mismatch, null if no issue * Returns warning message if mismatch, null if no issue

View File

@@ -12,6 +12,7 @@ import type {
ScanErrorType, ScanErrorType,
NfcTechType, NfcTechType,
NdefRecord, NdefRecord,
MifareClassicInfo,
} from '../../types/nfc'; } from '../../types/nfc';
/** /**
@@ -116,6 +117,56 @@ function parseAts(tag: TagEvent): {ats?: string; historicalBytes?: string} {
return {ats, historicalBytes}; return {ats, historicalBytes};
} }
/**
* Parse MIFARE Classic info from TagEvent (Android only)
*
* Uses NDEF maxSize to determine card capacity:
* - 4K: maxSize ~3356 bytes (NDEF capacity)
* - 1K: maxSize ~716 bytes (NDEF capacity)
* - Mini: maxSize ~80 bytes (NDEF capacity)
*/
function parseMifareClassic(tag: TagEvent): MifareClassicInfo | undefined {
const techTypes = tag.techTypes || [];
const isMifareClassic = techTypes.some(t => t.includes('MifareClassic'));
if (!isMifareClassic) {
return undefined;
}
// Use NDEF maxSize to determine card type
// MIFARE Classic 4K has ~3356 bytes NDEF capacity
// MIFARE Classic 1K has ~716 bytes NDEF capacity
const maxSize = (tag as any).maxSize;
if (typeof maxSize === 'number' && maxSize > 0) {
let size: number;
let sectorCount: number;
let blockCount: number;
if (maxSize >= 2000) {
// 4K card (NDEF maxSize ~3356)
size = 4096;
sectorCount = 40;
blockCount = 256;
} else if (maxSize >= 500) {
// 1K card (NDEF maxSize ~716)
size = 1024;
sectorCount = 16;
blockCount = 64;
} else {
// Mini card (NDEF maxSize ~80)
size = 320;
sectorCount = 5;
blockCount = 20;
}
console.log('[NFCManager] MIFARE Classic from maxSize:', {maxSize, size, sectorCount, blockCount});
return {size, sectorCount, blockCount};
}
return undefined;
}
/** /**
* Parse NDEF records from TagEvent * Parse NDEF records from TagEvent
*/ */
@@ -167,6 +218,7 @@ function tagEventToRawData(tag: TagEvent): RawTagData {
const atqa = parseAtqa(tag); const atqa = parseAtqa(tag);
const {ats, historicalBytes} = parseAts(tag); const {ats, historicalBytes} = parseAts(tag);
const ndefRecords = parseNdefRecords(tag); const ndefRecords = parseNdefRecords(tag);
const mifareClassic = parseMifareClassic(tag);
const isoDep = (tag as any).isoDep; const isoDep = (tag as any).isoDep;
const iso7816 = (tag as any).iso7816; const iso7816 = (tag as any).iso7816;
@@ -204,6 +256,7 @@ function tagEventToRawData(tag: TagEvent): RawTagData {
historicalBytes, historicalBytes,
maxTransceiveLength, maxTransceiveLength,
ndefRecords, ndefRecords,
mifareClassic,
}; };
} }
@@ -366,7 +419,10 @@ class NFCManagerService {
}; };
} }
return {tag: tagEventToRawData(tag)}; // Convert to RawTagData
const rawData = tagEventToRawData(tag);
return {tag: rawData};
} catch (error) { } catch (error) {
return {error: categorizeError(error)}; return {error: categorizeError(error)};
} finally { } finally {

View File

@@ -207,7 +207,8 @@ export async function transceiveNfcA(command: number[]): Promise<number[]> {
const response = await NfcManager.nfcAHandler.transceive(command); const response = await NfcManager.nfcAHandler.transceive(command);
return Array.from(response); return Array.from(response);
} catch (error) { } catch (error) {
console.error('[commands] NfcA transceive failed:', error); // Use debug level - some failures are expected (e.g., GET_VERSION on original Ultralight)
console.debug('[commands] NfcA transceive failed:', error);
throw error; throw error;
} }
} }

View File

@@ -32,6 +32,18 @@ export interface NdefRecord {
payload: number[]; payload: number[];
} }
/**
* MIFARE Classic specific info from Android
*/
export interface MifareClassicInfo {
/** Total memory size in bytes */
size: number;
/** Number of sectors */
sectorCount: number;
/** Total number of blocks */
blockCount: number;
}
/** /**
* Raw tag data from NFC scan * Raw tag data from NFC scan
*/ */
@@ -52,6 +64,8 @@ export interface RawTagData {
maxTransceiveLength?: number; maxTransceiveLength?: number;
/** Cached NDEF records (if tag supports NDEF) */ /** Cached NDEF records (if tag supports NDEF) */
ndefRecords?: NdefRecord[]; ndefRecords?: NdefRecord[];
/** MIFARE Classic info (Android only) */
mifareClassic?: MifareClassicInfo;
} }
/** /**