fix(blender): resolve ReferenceError in SeedBlender component

Adds the missing state declarations (`useState`) and handler functions for the dice input and final mixing steps.

The previous implementation included JSX that referenced these variables and functions before they were declared, causing a 'Can't find variable' runtime error. This commit defines the necessary state and logic to make the component fully functional.
This commit is contained in:
LC mac
2026-02-04 02:47:20 +08:00
parent 3f37596b3b
commit b918d88a47

View File

@@ -3,38 +3,68 @@
* @summary Main component for the Seed Blending feature.
*/
import { useState, useEffect } from 'react';
import { QrCode, X, Plus, CheckCircle2, AlertTriangle } from 'lucide-react';
import { QrCode, X, Plus, CheckCircle2, AlertTriangle, RefreshCw, Sparkles, EyeOff, Lock } from 'lucide-react';
import QRScanner from './QRScanner';
import { blendMnemonicsAsync, checkXorStrength, mnemonicToEntropy } from '../lib/seedblend';
import {
blendMnemonicsAsync,
checkXorStrength,
mnemonicToEntropy,
DiceStats,
calculateDiceStats,
detectBadPatterns,
diceToBytes,
hkdfExtractExpand,
entropyToMnemonic,
mixWithDiceAsync,
} from '../lib/seedblend';
// A simple debounce function
function debounce<F extends (...args: any[]) => any>(func: F, waitFor: number) {
let timeout: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<F>): Promise<ReturnType<F>> =>
new Promise(resolve => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => resolve(func(...args)), waitFor);
});
return (...args: Parameters<F>): void => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func(...args), waitFor);
};
}
export function SeedBlender() {
// Step 1 State
const [mnemonics, setMnemonics] = useState<string[]>(['']);
const [validity, setValidity] = useState<Array<boolean | null>>([null]);
const [showQRScanner, setShowQRScanner] = useState(false);
const [scanTargetIndex, setScanTargetIndex] = useState<number | null>(null);
// State for Step 2
const [blendedResult, setBlendedResult] = useState<{ blendedMnemonic12: string; blendedMnemonic24?: string; } | null>(null);
// Step 2 State
const [blendedResult, setBlendedResult] = useState<{ blendedEntropy: Uint8Array; blendedMnemonic12: string; blendedMnemonic24?: string; } | null>(null);
const [xorStrength, setXorStrength] = useState<{ isWeak: boolean; uniqueBytes: number; } | null>(null);
const [blendError, setBlendError] = useState<string>('');
const [blending, setBlending] = useState(false);
// Step 3 State
const [diceRolls, setDiceRolls] = useState('');
const [diceStats, setDiceStats] = useState<DiceStats | null>(null);
const [dicePatternWarning, setDicePatternWarning] = useState<string | null>(null);
const [diceOnlyMnemonic, setDiceOnlyMnemonic] = useState<string | null>(null);
// Effect to validate and blend mnemonics
// Step 4 State
const [finalMnemonic, setFinalMnemonic] = useState<string | null>(null);
const [mixing, setMixing] = useState(false);
// Main clear function
const handleLockAndClear = () => {
setMnemonics(['']);
setValidity([null]);
setBlendedResult(null);
setXorStrength(null);
setBlendError('');
setDiceRolls('');
setDiceStats(null);
setDicePatternWarning(null);
setDiceOnlyMnemonic(null);
setFinalMnemonic(null);
}
// Effect to validate and blend mnemonics (Step 2)
useEffect(() => {
const processMnemonics = async () => {
setBlending(true);
@@ -84,12 +114,34 @@ export function SeedBlender() {
setBlending(false);
};
// Debounce the processing to avoid running on every keystroke
const debouncedProcess = debounce(processMnemonics, 300);
debouncedProcess();
debounce(processMnemonics, 300)();
}, [mnemonics]);
// Effect to process dice rolls (Step 3)
useEffect(() => {
const processDice = async () => {
setDiceStats(calculateDiceStats(diceRolls));
const pattern = detectBadPatterns(diceRolls);
setDicePatternWarning(pattern.message || null);
if (diceRolls.length >= 50) {
try {
const diceBytes = diceToBytes(diceRolls);
const outputByteLength = blendedResult?.blendedEntropy.length === 32 ? 32 : 16;
const diceOnlyEntropy = await hkdfExtractExpand(diceBytes, outputByteLength, new TextEncoder().encode('dice-only'));
const mnemonic = await entropyToMnemonic(diceOnlyEntropy);
setDiceOnlyMnemonic(mnemonic);
} catch (e) {
setDiceOnlyMnemonic(null);
}
} else {
setDiceOnlyMnemonic(null);
}
};
debounce(processDice, 200)();
}, [diceRolls, blendedResult]);
const handleAddMnemonic = () => {
setMnemonics([...mnemonics, '']);
@@ -127,6 +179,24 @@ export function SeedBlender() {
setScanTargetIndex(null);
};
const handleFinalMix = async () => {
if (!blendedResult) return;
setMixing(true);
try {
const outputBits = blendedResult.blendedEntropy.length === 32 ? 256 : 128;
const result = await mixWithDiceAsync(blendedResult.blendedEntropy, diceRolls, outputBits);
setFinalMnemonic(result.finalMnemonic);
} catch(e) {
// handle error
} finally {
setMixing(false);
}
};
const handleClearFinal = () => {
setFinalMnemonic(null);
}
const getBorderColor = (isValid: boolean | null) => {
if (isValid === true) return 'border-green-500 focus:ring-green-500';
if (isValid === false) return 'border-red-500 focus:ring-red-500';
@@ -139,6 +209,13 @@ export function SeedBlender() {
{/* Header */}
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-slate-100">Seed Blender</h2>
<button
onClick={handleLockAndClear}
className="flex items-center gap-2 text-sm text-red-400 bg-slate-800/50 px-3 py-1.5 rounded-lg hover:bg-red-900/50 transition-colors"
>
<Lock size={16} />
<span>Lock/Clear</span>
</button>
</div>
{/* Step 1: Input Mnemonics */}