/
DnTyr
/
Password-Development
Обзор
Документация
Войти
/
DnTyr
/
Password-Development
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/src/hooks/useGenerator.js
229 строк
6 KB
DT
first_commit
03 июн 2026, 10:22
03 июн 2026, 10:22
357cb35
Код
Авторство
О чём код?
import { useState, useEffect, useCallback, useRef } from "react"; import { passwordService } from "../services/passwordService"; import { toastError, toastSuccess, toastWarning } from "../utils/toast"; import { ShieldCheck } from 'lucide-react'; import { useTranslation } from "react-i18next"; /** Длительность до очистки буфера после копирования (сек.) */ export const CLIPBOARD_CLEAR_SECONDS = 60; export function useGenerator() { const [length, setLength] = useState(16); const [generatedPassword, setGeneratedPassword] = useState(""); const [manualPassword, setManualPassword] = useState(""); const [mode, setMode] = useState("manual"); const password = mode === "generated" ? generatedPassword : manualPassword; const [isGenerated, setIsGenerated] = useState(false); const [uppercase, setUppercase] = useState(true); const [numbers, setNumbers] = useState(true); const [symbols, setSymbols] = useState(true); const [loading, setLoading] = useState(false); const [cooldown, setCooldown] = useState(false); const [clipboardSecondsLeft, setClipboardSecondsLeft] = useState(null); const clipboardTickRef = useRef(null); const clearClipboardTimers = useCallback(() => { if (clipboardTickRef.current) { clearInterval(clipboardTickRef.current); clipboardTickRef.current = null; } }, []); useEffect(() => { const saved = localStorage.getItem("generatorCooldownUntil"); if (!saved) return; const remaining = Number(saved) - Date.now(); if (remaining > 0) { setCooldown(true); setTimeout(() => setCooldown(false), remaining); } else { localStorage.removeItem("generatorCooldownUntil"); } }, []); useEffect(() => { return () => clearClipboardTimers(); }, [clearClipboardTimers]); const COOLDOWN_MS = 3000; const { t } = useTranslation(); // ========================= // GENERATE // ========================= const generatePassword = useCallback(async (manualIsPassphrase = null) => { const numericLength = Number(length); // 1. Проверяем, что введено именно целое число (любой длины) if (!/^\d+$/.test(String(length))) { toastError(t("Укажите число")); return; } // 2. Сначала проверяем жесткий максимум (сработает и на 129, и на 1000+) if (numericLength > 128) { toastError(t("Максимум 128 символов")); return; } // 3. Проверяем жесткий минимум if (numericLength < 12) { toastError(t("Минимум 12 символов")); return; } // 4. Просто предупреждаем, если длина от 12 до 15 (не прерывая выполнение) if (numericLength < 16) { toastWarning(t("Небезопасная длина пароля")); } // 5. Проверка чекбоксов const isActuallyPassphrase = manualIsPassphrase !== null ? manualIsPassphrase : numericLength > 32; if (!isActuallyPassphrase && numericLength <= 32 && !uppercase && !numbers && !symbols) { toastError(t("Выберите хотя бы один тип символов")); return; } try { setLoading(true); const data = await passwordService.generate({ length: numericLength, uppercase, numbers, symbols, is_passphrase: manualIsPassphrase }); if (data?.password) { setGeneratedPassword(data.password); setManualPassword(""); setMode("generated"); setIsGenerated(true); } } catch (err) { const status = err?.status; const data = err?.data; const isRateLimit = status === 429 || data?.type === "rate" || data?.error === "rate_limit"; if (isRateLimit) { toastWarning(t("Слишком быстро!")); // Включаем блокировку кнопки setCooldown(true); const cooldownEnd = Date.now() + COOLDOWN_MS; localStorage.setItem("generatorCooldownUntil", cooldownEnd); setTimeout(() => { setCooldown(false); localStorage.removeItem("generatorCooldownUntil"); }, COOLDOWN_MS); return; } if (status === 400) { toastError(data?.error || t("Неверный запрос")); return; } if (status === 503) { toastError(t("Сервис временно перегружен")); return; } toastError(t("Ошибка сервера, попробуйте позже")); } finally { setLoading(false); } }, [length, uppercase, numbers, symbols, t]); // ========================= // COPY // ========================= const copyPassword = useCallback(async () => { if (!password) return; try { await navigator.clipboard.writeText(password); toastSuccess(t("Пароль скопирован")); } catch { toastError(t("Не удалось скопировать")); return; } clearClipboardTimers(); setClipboardSecondsLeft(CLIPBOARD_CLEAR_SECONDS); clipboardTickRef.current = setInterval(() => { setClipboardSecondsLeft((s) => { if (s === null) return null; if (s <= 1) { clearClipboardTimers(); (async () => { try { await navigator.clipboard.writeText(""); } catch {} toastSuccess(t("Буфер очищен для вашей безопасности"), { icon: <ShieldCheck size={20} color="#2852a7" />, }); })(); return null; } return s - 1; }); }, 1000); }, [password, clearClipboardTimers, t]); // ========================= // MANUAL INPUT // ========================= const setPassword = useCallback((value) => { setManualPassword(value); setMode("manual"); setIsGenerated(false); }, []); return { length, setLength, password, setPassword, isGenerated, setIsGenerated, uppercase, setUppercase, numbers, setNumbers, symbols, setSymbols, loading, cooldown, generatePassword, copyPassword, clipboardSecondsLeft, mode, }; }