Private
Public Access
1
0

chg: usr: replace Google ReCaptcha with Cap instance #13

This commit is contained in:
2026-06-01 22:24:34 +02:00
parent 199bb7e525
commit 7063704539
24 changed files with 389 additions and 376 deletions
@@ -7,17 +7,16 @@
* file that was distributed with this source code.
*/
import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import 'cap-widget';
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { func, node, string } from 'prop-types';
const CAPTCHA_STORAGE_KEY = 'mineseeker_captcha_verified';
const CAPTCHA_TOKEN_KEY = 'mineseeker_captcha_token';
const RECAPTCHA_ACTION = 'mineseeker_play';
const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
const CaptchaOverlay = ({ apiEndpoint, onVerified, children }) => {
const [verified, setVerified] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const capRef = useRef(null);
const handleToken = useCallback(token => {
const wrapper = document.getElementById('mine-wrapper');
@@ -30,15 +29,9 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
onVerified?.();
}, [onVerified]);
const buttonClasses = useMemo(() => [
'captcha-button',
error && 'captcha-button--error',
loading && 'captcha-button--loading',
].filter(Boolean).join(' '), [error, loading]);
useEffect(() => {
const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY);
const storedTime = sessionStorage.getItem(CAPTCHA_STORAGE_KEY);
const storedTime = sessionStorage.getItem(CAPTCHA_STORAGE_KEY);
if (storedToken && storedTime) {
const elapsed = (Date.now() - parseInt(storedTime)) / 1000;
@@ -52,40 +45,42 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
return;
}
}
}, [onVerified]);
if (window.grecaptcha) {
window.grecaptcha.ready(() => {
window.grecaptcha
.execute(siteKey, { action: RECAPTCHA_ACTION })
.then(token => {
handleToken(token);
})
.catch(() => {
setError(true);
});
});
}
}, [siteKey, onVerified, handleToken]);
useEffect(() => {
const widget = document.createElement('cap-widget');
widget.style.display = 'none';
capRef.current = widget;
document.body.appendChild(widget);
const cap = new window.Cap({ apiEndpoint }, widget);
let cancelled = false;
const handleClick = () => {
setLoading(true);
setError(false);
const run = async () => {
try {
const result = await cap.solve();
if (!cancelled && result?.token) {
handleToken(result.token);
}
} catch (_) {
if (!cancelled) {
setTimeout(() => {
if (!cancelled) {
run();
}
}, 1200);
}
}
};
window.grecaptcha.ready(() => {
window.grecaptcha
.execute(siteKey, { action: RECAPTCHA_ACTION })
.then(token => {
handleToken(token);
setLoading(false);
})
.catch(() => {
setLoading(false);
setError(true);
setTimeout(() => setError(false), 2000);
});
});
};
run();
return () => {
cancelled = true;
widget.remove();
capRef.current = null;
};
}, [handleToken]);
if (verified) {
return <Fragment>{children}</Fragment>;
@@ -99,16 +94,8 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
</div>
<h1 className="captcha-title">Ready to Play?</h1>
<p className="captcha-description">
Click below to verify you&apos;re human and start playing.
Verifying your session...
</p>
<button
className={buttonClasses}
onClick={handleClick}
disabled={loading}
>
<i className={`fa ${loading ? 'fa-spinner fa-spin' : error ? 'fa-exclamation-circle' : 'fa-play'}`} />
{loading ? 'Verifying...' : error ? 'Try Again' : 'Start Playing'}
</button>
</div>
</div>
);
@@ -117,7 +104,7 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
export default CaptchaOverlay;
CaptchaOverlay.propTypes = {
siteKey: string.isRequired,
onVerified: func,
children: node,
apiEndpoint: string.isRequired,
onVerified: func,
children: node,
};
@@ -19,7 +19,7 @@ export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDe
const { onClick, resign } = useServerCommunication(gameAssoc, gameInherited, opponentName, isEnvDev);
const [captchaVerified, setCaptchaVerified] = useState(false);
const siteKey = document.getElementById('mine-wrapper')?.dataset.recaptchaSiteKey;
const apiEndpoint = document.getElementById('mine-wrapper')?.dataset.capApiEndpoint;
if (!gridReady) {
return (
@@ -29,9 +29,9 @@ export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDe
);
}
if (!captchaVerified && siteKey) {
if (!captchaVerified && apiEndpoint) {
return (
<CaptchaOverlay siteKey={siteKey} onVerified={() => setCaptchaVerified(true)} />
<CaptchaOverlay apiEndpoint={apiEndpoint} onVerified={() => setCaptchaVerified(true)} />
);
}