fix(qr): resolve scanner race condition and crashes

This commit addresses several issues related to the QR code scanner:

- Fixes a build failure by defining stable handlers (`handleRestoreClose`, `handleRestoreError`) for the QRScanner component in `App.tsx` using `useCallback`.
- Resolves a race condition that caused an `AbortError` when the scanner was initialized, particularly in React Strict Mode. This was fixed by ensuring all props passed to the scanner are stable.
- Implements more robust error handling within the `QRScanner` component to prevent crashes when `null` or `undefined` errors are caught.
- Updates documentation (`README.md`, `GEMINI.md`) to version 1.4.5.
This commit is contained in:
LC mac
2026-02-07 13:46:02 +08:00
parent a021044a19
commit cf3412b235
4 changed files with 60 additions and 33 deletions

View File

@@ -403,14 +403,21 @@ function App() {
setShowQRScanner(false);
}, []);
const handleRestoreError = useCallback((error: string) => {
setError(error);
const handleRestoreError = useCallback((err: string) => {
setError(err);
setShowQRScanner(false);
}, []);
return (
<div className="min-h-screen bg-slate-800 text-slate-100">
<Header
@@ -517,7 +524,7 @@ function App() {
{privateKeyInput && (
<div className="space-y-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">Private Key Passphrase</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Private Key Passphrase</label>
<div className="relative">
<Lock className="absolute left-3 top-3 text-slate-400" size={16} />
<input
@@ -555,18 +562,18 @@ function App() {
{/* Encryption Mode Toggle */}
<div className="space-y-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">Encryption Mode</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Encryption Mode</label>
<select
value={encryptionMode}
onChange={(e) => setEncryptionMode(e.target.value as 'pgp' | 'krux' | 'seedqr')}
disabled={isReadOnly}
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all"
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all"
>
<option value="pgp">PGP (Asymmetric)</option>
<option value="krux">Krux KEF (Passphrase)</option>
<option value="seedqr">SeedQR (Unencrypted)</option>
</select>
<p className="text-[10px] text-slate-500 mt-1">
<p className="text-[10px] text-slate-800 mt-1">
{encryptionMode === 'pgp'
? 'Uses PGP keys or password'
: 'Uses passphrase only (Krux compatible)'}
@@ -576,17 +583,17 @@ function App() {
{/* SeedQR Format Toggle */}
{encryptionMode === 'seedqr' && (
<div className="space-y-2 pt-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">SeedQR Format</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">SeedQR Format</label>
<select
value={seedQrFormat}
onChange={(e) => setSeedQrFormat(e.target.value as 'standard' | 'compact')}
disabled={isReadOnly}
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all"
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all"
>
<option value="standard">Standard (Numeric)</option>
<option value="compact">Compact (Binary)</option>
</select>
<p className="text-[10px] text-slate-500 mt-1">
<p className="text-[10px] text-slate-800 mt-1">
{seedQrFormat === 'standard'
? 'Numeric format, human-readable.'
: 'Compact binary format, smaller QR code.'}
@@ -598,7 +605,7 @@ function App() {
{encryptionMode === 'krux' && activeTab === 'backup' && (
<>
<div className="space-y-2 pt-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">Krux Label</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Krux Label</label>
<div className="relative">
<input
type="text"
@@ -610,11 +617,11 @@ function App() {
readOnly={isReadOnly}
/>
</div>
<p className="text-[10px] text-slate-500 mt-1">Label for identification (max 252 bytes)</p>
<p className="text-[10px] text-slate-800 mt-1">Label for identification (max 252 bytes)</p>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">PBKDF2 Iterations</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">PBKDF2 Iterations</label>
<div className="relative">
<input
type="number"
@@ -628,13 +635,13 @@ function App() {
readOnly={isReadOnly}
/>
</div>
<p className="text-[10px] text-slate-500 mt-1">Higher = more secure but slower (default: 200,000)</p>
<p className="text-[10px] text-slate-800 mt-1">Higher = more secure but slower (default: 200,000)</p>
</div>
</>
)}
<div className="space-y-2">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">Message Password</label>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Message Password</label>
<div className="relative">
<Lock className="absolute left-3 top-3 text-slate-400" size={16} />
<input
@@ -647,7 +654,7 @@ function App() {
readOnly={isReadOnly}
/>
</div>
<p className="text-[10px] text-slate-500 mt-1">
<p className="text-[10px] text-slate-800 mt-1">
{encryptionMode === 'krux'
? 'Required passphrase for Krux encryption'
: 'Symmetric encryption password (SKESK)'}
@@ -713,7 +720,7 @@ function App() {
</div>
<div className="space-y-2">
<div className="flex items-center justify-between gap-3">
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">
Raw payload (copy for backup)
</label>