mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user