Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.0 kB · 160 lines
TSX
at mistress
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161import { deviceName } from "expo-device";import { useEffect, useState } from "react";import { ActivityIndicator, Text, View } from "react-native";import { NitroTotp, parseSecretKey } from "react-native-nitro-totp";import QRCode from "react-native-qrcode-svg";import { useAuth } from "./SessionProvider";
const totp = new NitroTotp();const PERIOD_MS = 30_000;
function totpWindow(now = Date.now()) { return Math.floor(now / PERIOD_MS);}
function timeLeftInWindow(now = Date.now()) { return PERIOD_MS - (now % PERIOD_MS);}
type TotpResult = { seed: string; code: string;};
export default function QRcode() { const { seed } = useAuth();
const [totpResult, setTotpResult] = useState<TotpResult | null>(null);
const [macId] = useState("Unavailable"); const [error, setError] = useState<string | null>(null);
const code = seed && totpResult?.seed === seed ? totpResult.code : "";
useEffect(() => { if (!seed) return;
let active = true; let shownWindow = totpWindow(); let frame = 0;
const generate = () => { try { const secret = parseSecretKey(seed); const nextCode = totp.generate(secret);
if (active) { setTotpResult({ seed, code: nextCode }); setError(null); } } catch { if (active) { setError("Unable to generate TOTP. Check the seed."); } } };
generate(); const watch = () => { const currentWindow = totpWindow();
if (currentWindow !== shownWindow && active) { shownWindow = currentWindow; generate(); }
frame = requestAnimationFrame(watch); };
frame = requestAnimationFrame(watch);
return () => { active = false; cancelAnimationFrame(frame); }; }, [seed]);
if (!seed) { return ( <View className="items-center justify-center p-6"> <Text className="text-base text-red-600">No seed available.</Text> </View> ); }
const qrValue = code ? JSON.stringify({ deviceName, macId, totp: code, }) : "";
return ( <View className="items-center"> <View className="w-full max-w-sm items-center rounded-3xl bg-green-700 p-6 shadow-md"> <Text className="mb-2 text-xl font-bold text-white">Touch Fluffy Tail With Me :3</Text>
{error ? ( <Text className="my-4 text-center text-red-600">{error}</Text> ) : code ? ( <> <TotpCountdown />
<View className="mb-5 rounded-xl bg-white p-3"> <QRCode value={qrValue} size={300} /> </View> <TotpProgress /> </> ) : ( <ActivityIndicator className="my-6" size="large" color="#1d4ed8" /> )}
<View className="w-full gap-2"> <Text className="text-sm text-white">Device name</Text>
<Text className="font-medium text-slate-900"> {deviceName ? deviceName : "Unknown device"} </Text> </View> </View> </View> );}
function TotpCountdown() { const [secondsLeft, setSecondsLeft] = useState(() => Math.ceil(timeLeftInWindow() / 1000));
useEffect(() => { const timer = setInterval(() => { setSecondsLeft(Math.ceil(timeLeftInWindow() / 1000)); }, 200);
return () => clearInterval(timer); }, []);
return <Text className="mb-5 mt-2 text-sm text-white">Refreshes in {secondsLeft}s</Text>;}
function TotpProgress() { const [elapsed, setElapsed] = useState(() => 1 - timeLeftInWindow() / PERIOD_MS);
useEffect(() => { let frame = 0;
const tick = () => { setElapsed(1 - timeLeftInWindow() / PERIOD_MS); frame = requestAnimationFrame(tick); };
frame = requestAnimationFrame(tick);
return () => cancelAnimationFrame(frame); }, []);
return ( <View className="h-2 w-full overflow-hidden rounded-full bg-white/30"> <View className="h-full rounded-full bg-white" style={{ width: `${elapsed * 100}%` }} /> </View> );}