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). // همه‌ی attributeها با setAttribute ست می‌شوند، نه JSX prop. declare module 'react' { namespace JSX { interface IntrinsicElements { 'altcha-widget': React.DetailedHTMLProps, HTMLElement>; } } } 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 (challenge/language/auto) را با setAttribute ست می‌کنیم، نه JSX prop — // React ممکن است پراپ custom element را به‌صورت property ست کند و widget آن را نخواند. 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); } }; // widget v3 نام attribute را از challengeurl به challenge تغییر داده (docs v2)؛ // challengeurl را observe نمی‌کند و بدون URL، صفحه‌ی جاری (HTML) fetch می‌شد. el.setAttribute('challenge', 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 } />; }