mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-06 17:37:51 +08:00
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:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user