mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +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.
|
* @summary Main component for the Seed Blending feature.
|
||||||
*/
|
*/
|
||||||
import { useState, useEffect } from 'react';
|
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 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
|
// A simple debounce function
|
||||||
function debounce<F extends (...args: any[]) => any>(func: F, waitFor: number) {
|
function debounce<F extends (...args: any[]) => any>(func: F, waitFor: number) {
|
||||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
return (...args: Parameters<F>): void => {
|
||||||
return (...args: Parameters<F>): Promise<ReturnType<F>> =>
|
if (timeout) clearTimeout(timeout);
|
||||||
new Promise(resolve => {
|
timeout = setTimeout(() => func(...args), waitFor);
|
||||||
if (timeout) {
|
};
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout = setTimeout(() => resolve(func(...args)), waitFor);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SeedBlender() {
|
export function SeedBlender() {
|
||||||
|
// Step 1 State
|
||||||
const [mnemonics, setMnemonics] = useState<string[]>(['']);
|
const [mnemonics, setMnemonics] = useState<string[]>(['']);
|
||||||
const [validity, setValidity] = useState<Array<boolean | null>>([null]);
|
const [validity, setValidity] = useState<Array<boolean | null>>([null]);
|
||||||
const [showQRScanner, setShowQRScanner] = useState(false);
|
const [showQRScanner, setShowQRScanner] = useState(false);
|
||||||
const [scanTargetIndex, setScanTargetIndex] = useState<number | null>(null);
|
const [scanTargetIndex, setScanTargetIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
// State for Step 2
|
// Step 2 State
|
||||||
const [blendedResult, setBlendedResult] = useState<{ blendedMnemonic12: string; blendedMnemonic24?: string; } | null>(null);
|
const [blendedResult, setBlendedResult] = useState<{ blendedEntropy: Uint8Array; blendedMnemonic12: string; blendedMnemonic24?: string; } | null>(null);
|
||||||
const [xorStrength, setXorStrength] = useState<{ isWeak: boolean; uniqueBytes: number; } | null>(null);
|
const [xorStrength, setXorStrength] = useState<{ isWeak: boolean; uniqueBytes: number; } | null>(null);
|
||||||
const [blendError, setBlendError] = useState<string>('');
|
const [blendError, setBlendError] = useState<string>('');
|
||||||
const [blending, setBlending] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const processMnemonics = async () => {
|
const processMnemonics = async () => {
|
||||||
setBlending(true);
|
setBlending(true);
|
||||||
@@ -84,12 +114,34 @@ export function SeedBlender() {
|
|||||||
setBlending(false);
|
setBlending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debounce the processing to avoid running on every keystroke
|
debounce(processMnemonics, 300)();
|
||||||
const debouncedProcess = debounce(processMnemonics, 300);
|
|
||||||
debouncedProcess();
|
|
||||||
|
|
||||||
}, [mnemonics]);
|
}, [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 = () => {
|
const handleAddMnemonic = () => {
|
||||||
setMnemonics([...mnemonics, '']);
|
setMnemonics([...mnemonics, '']);
|
||||||
@@ -127,6 +179,24 @@ export function SeedBlender() {
|
|||||||
setScanTargetIndex(null);
|
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) => {
|
const getBorderColor = (isValid: boolean | null) => {
|
||||||
if (isValid === true) return 'border-green-500 focus:ring-green-500';
|
if (isValid === true) return 'border-green-500 focus:ring-green-500';
|
||||||
if (isValid === false) return 'border-red-500 focus:ring-red-500';
|
if (isValid === false) return 'border-red-500 focus:ring-red-500';
|
||||||
@@ -139,6 +209,13 @@ export function SeedBlender() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold text-slate-100">Seed Blender</h2>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Step 1: Input Mnemonics */}
|
{/* Step 1: Input Mnemonics */}
|
||||||
|
|||||||
Reference in New Issue
Block a user