import React, { useEffect, useRef, useState } from 'react'; import 'altcha'; import 'altcha/i18n/fa'; // ثبت locale رسمی فارسی → با language="fa" فعال می‌شود // ALTCHA خودمیزبان: widget با گرفتن challenge از بک‌اند، proof-of-work را در // پس‌زمینه‌ی مرورگر حل می‌کند و مقدار base64 حل‌شده را در event `verified` می‌دهد. // این مقدار باید در بدنه‌ی درخواستِ endpoint عمومی با کلید `altcha` ارسال شود. interface AltchaProps { onVerified: (payload: string) => void; challengeUrl?: string; } // altcha-widget یک custom element است؛ به JSX معرفی می‌شود (React 19: namespace زیر React.JSX). declare module 'react' { namespace JSX { interface IntrinsicElements { 'altcha-widget': React.DetailedHTMLProps, HTMLElement> & { challengeurl?: string; strings?: string; }; } } } export default function Altcha({ onVerified, challengeUrl = '/api/v1/altcha/challenge' }: AltchaProps) { const ref = useRef(null); // null=در حال بررسی وضعیت، true/false=فعال/غیرفعال بودن کپچا در سرور const [enabled, setEnabled] = useState(null); useEffect(() => { let alive = true; fetch('/api/v1/altcha/config') .then((r) => r.json()) .then((c) => { if (!alive) return; setEnabled(!!c.enabled); if (!c.enabled) onVerified(''); // کپچا خاموش → فرم بدون مانع ارسال شود }) .catch(() => { if (alive) setEnabled(false); }); return () => { alive = false; }; }, [onVerified]); // config (challengeurl/strings/auto) را با setAttribute ست می‌کنیم، نه JSX prop — // React ممکن است پراپ custom element را به‌صورت property ست کند و widget آن را // نخواند؛ نتیجه challengeurl خالی و fetch صفحه‌ی جاری (HTML) به‌جای JSON بود. useEffect(() => { if (enabled !== true) return; const el = ref.current; if (!el) return; const onStateChange = (e: Event) => { const detail = (e as CustomEvent).detail as { state?: string; payload?: string }; if (detail?.state === 'verified' && detail.payload) { onVerified(detail.payload); } }; el.setAttribute('challengeurl', challengeUrl); el.setAttribute('language', 'fa'); el.setAttribute('auto', 'onload'); el.addEventListener('statechange', onStateChange); return () => el.removeEventListener('statechange', onStateChange); }, [onVerified, enabled, challengeUrl]); if (enabled !== true) return null; return } />; }