chg: dev: protect the gameplay with recaptcha #4
This commit is contained in:
169
assets/js/mine-seeker/components/CaptchaOverlay.jsx
Normal file
169
assets/js/mine-seeker/components/CaptchaOverlay.jsx
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
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 [verified, setVerified] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY);
|
||||
const storedTime = sessionStorage.getItem(CAPTCHA_STORAGE_KEY);
|
||||
|
||||
if (storedToken && storedTime) {
|
||||
const elapsed = (Date.now() - parseInt(storedTime)) / 1000;
|
||||
if (110 > elapsed) {
|
||||
const wrapper = document.getElementById('mine-wrapper');
|
||||
if (wrapper) {
|
||||
wrapper.dataset.captchaToken = storedToken;
|
||||
}
|
||||
setVerified(true);
|
||||
onVerified?.();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.grecaptcha) {
|
||||
window.grecaptcha.ready(() => {
|
||||
window.grecaptcha
|
||||
.execute(siteKey, { action: RECAPTCHA_ACTION })
|
||||
.then(token => {
|
||||
handleToken(token);
|
||||
})
|
||||
.catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [siteKey, onVerified]);
|
||||
|
||||
const handleToken = token => {
|
||||
const wrapper = document.getElementById('mine-wrapper');
|
||||
if (wrapper) {
|
||||
wrapper.dataset.captchaToken = token;
|
||||
}
|
||||
sessionStorage.setItem(CAPTCHA_TOKEN_KEY, token);
|
||||
sessionStorage.setItem(CAPTCHA_STORAGE_KEY, Date.now().toString());
|
||||
setVerified(true);
|
||||
onVerified?.();
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (verified) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const overlayStyles = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'rgba(7, 9, 13, 0.95)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1000,
|
||||
};
|
||||
|
||||
const contentStyles = {
|
||||
textAlign: 'center',
|
||||
color: '#fff',
|
||||
maxWidth: '400px',
|
||||
padding: '40px',
|
||||
};
|
||||
|
||||
const iconStyles = {
|
||||
fontSize: '64px',
|
||||
color: '#236f87',
|
||||
marginBottom: '24px',
|
||||
};
|
||||
|
||||
const h1Styles = {
|
||||
font: '800 32px Rajdhani, sans-serif',
|
||||
margin: '0 0 16px',
|
||||
letterSpacing: '1px',
|
||||
};
|
||||
|
||||
const pStyles = {
|
||||
color: 'rgba(149, 207, 245, 0.7)',
|
||||
font: '400 16px Rajdhani, sans-serif',
|
||||
margin: '0 0 32px',
|
||||
letterSpacing: '0.5px',
|
||||
};
|
||||
|
||||
const buttonStyles = {
|
||||
background: error
|
||||
? 'linear-gradient(#8a2323 0%, #681a1a 100%)'
|
||||
: loading
|
||||
? 'linear-gradient(#236f87 0%, #1a5068 100%)'
|
||||
: 'linear-gradient(#236f87 0%, #1a5068 100%)',
|
||||
border: `2px solid ${error ? '#9a2e2e' : loading ? '#2e7a9a' : '#2e7a9a'}`,
|
||||
borderRadius: '8px',
|
||||
color: '#e0f4ff',
|
||||
cursor: loading ? 'wait' : 'pointer',
|
||||
font: '800 18px Rajdhani, sans-serif',
|
||||
letterSpacing: '2px',
|
||||
padding: '16px 40px',
|
||||
textTransform: 'uppercase',
|
||||
transition: 'all 0.3s ease',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
opacity: loading ? 0.7 : 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={overlayStyles}>
|
||||
<div style={contentStyles}>
|
||||
<div style={iconStyles}>
|
||||
<i className="fa fa-shield-halved" />
|
||||
</div>
|
||||
<h1 style={h1Styles}>Ready to Play?</h1>
|
||||
<p style={pStyles}>
|
||||
Click below to verify you're human and start playing.
|
||||
</p>
|
||||
<button
|
||||
style={buttonStyles}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaptchaOverlay;
|
||||
Reference in New Issue
Block a user