feat: fix CompactSeedQR binary QR code scanning with jsQR library

- Replace BarcodeDetector with jsQR for raw binary byte access
- BarcodeDetector forced UTF-8 decoding which corrupted binary data
- jsQR's binaryData property preserves raw bytes without text conversion
- Fix regex bug: use single backslash \x00 instead of \x00 for binary detection
- Add debug logging for scan data inspection
- QR generation already worked (Krux-compatible), only scanning was broken

Resolves binary QR code scanning for 12/24-word CompactSeedQR format.
Tested with Krux device - full bidirectional compatibility confirmed.
This commit is contained in:
LC mac
2026-02-07 04:22:56 +08:00
parent 49d73a7ae4
commit aa06c9ae27
39 changed files with 4664 additions and 777 deletions

View File

@@ -3,39 +3,85 @@ import { Download } from 'lucide-react';
import QRCode from 'qrcode';
interface QrDisplayProps {
value: string;
value: string | Uint8Array;
}
export const QrDisplay: React.FC<QrDisplayProps> = ({ value }) => {
const [dataUrl, setDataUrl] = useState<string>('');
const [dataUrl, setDataUrl] = useState('');
const [debugInfo, setDebugInfo] = useState('');
useEffect(() => {
if (value) {
QRCode.toDataURL(value, {
errorCorrectionLevel: 'M',
type: 'image/png',
width: 512,
margin: 4,
color: {
dark: '#000000',
light: '#FFFFFF'
}
})
.then(setDataUrl)
.catch(console.error);
if (!value) {
setDataUrl('');
return;
}
const generateQR = async () => {
try {
console.log('🎨 QrDisplay generating QR for:', value);
console.log(' - Type:', value instanceof Uint8Array ? 'Uint8Array' : typeof value);
console.log(' - Length:', value.length);
if (value instanceof Uint8Array) {
console.log(' - Hex:', Array.from(value).map(b => b.toString(16).padStart(2, '0')).join(''));
// Create canvas manually for precise control
const canvas = document.createElement('canvas');
// Use the toCanvas method with Uint8Array directly
await QRCode.toCanvas(canvas, [{
data: value,
mode: 'byte'
}], {
errorCorrectionLevel: 'L',
width: 512,
margin: 4,
color: {
dark: '#000000',
light: '#FFFFFF'
}
});
const url = canvas.toDataURL('image/png');
setDataUrl(url);
setDebugInfo(`Binary QR: ${value.length} bytes`);
console.log('✅ Binary QR generated successfully');
} else {
// For string data
console.log(' - String data:', value.slice(0, 50));
const url = await QRCode.toDataURL(value, {
errorCorrectionLevel: 'L',
type: 'image/png',
width: 512,
margin: 4,
color: {
dark: '#000000',
light: '#FFFFFF'
}
});
setDataUrl(url);
setDebugInfo(`String QR: ${value.length} chars`);
console.log('✅ String QR generated successfully');
}
} catch (err) {
console.error('❌ QR generation error:', err);
setDebugInfo(`Error: ${err}`);
}
};
generateQR();
}, [value]);
const handleDownload = () => {
if (!dataUrl) return;
// Generate filename: SeedPGP_YYYY-MM-DD_HHMMSS.png
const now = new Date();
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD
const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // HHMMSS
const date = now.toISOString().split('T')[0];
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
const filename = `SeedPGP_${date}_${time}.png`;
// Create download link
const link = document.createElement('a');
link.href = dataUrl;
link.download = filename;
@@ -47,25 +93,27 @@ export const QrDisplay: React.FC<QrDisplayProps> = ({ value }) => {
if (!dataUrl) return null;
return (
<div className="flex flex-col items-center gap-4">
<div className="flex items-center justify-center p-4 bg-white rounded-xl border-2 border-slate-200">
<img
src={dataUrl}
alt="SeedPGP QR Code"
className="w-80 h-80"
/>
<div className="space-y-4">
<div className="bg-white p-6 rounded-lg inline-block shadow-lg">
<img src={dataUrl} alt="QR Code" className="w-full h-auto" />
</div>
{debugInfo && (
<div className="text-xs text-slate-500 font-mono">
{debugInfo}
</div>
)}
<button
onClick={handleDownload}
className="inline-flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-lg font-semibold hover:from-green-700 hover:to-green-800 transition-all shadow-lg hover:shadow-xl"
className="flex items-center gap-2 px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg transition-colors"
>
<Download size={18} />
<Download size={16} />
Download QR Code
</button>
<p className="text-xs text-slate-500 text-center max-w-sm">
Downloads as: SeedPGP_2026-01-28_231645.png
<p className="text-xs text-slate-500">
Downloads as: SeedPGP_{new Date().toISOString().split('T')[0]}_HHMMSS.png
</p>
</div>
);