Rewrite to vanilla JS

This commit is contained in:
Ivan Nikolskiy 2025-06-09 17:37:19 +02:00
parent a53fada7e1
commit 0759b43efc
4 changed files with 96 additions and 45 deletions

View File

@ -23,6 +23,8 @@ SOFTWARE.
"""
import time
import json
from urllib.parse import urlencode
from functools import wraps
@ -75,10 +77,13 @@ def hoptcha_protected(
if isinstance(key, str):
key_func = BUILTIN_KEYS.get(key)
if not key_func:
raise ValueError(f"Unknown key: {key}")
elif callable(key):
key_func = key
else:
raise TypeError("key must be a string or callable")
@ -101,7 +106,19 @@ def hoptcha_protected(
attempts = cache.get(cache_key, 0)
if attempts >= threshold:
token = request.POST.get("captcha_token") or request.GET.get("captcha_token")
token = (
request.POST.get("captcha_token") or
request.GET.get("captcha_token")
)
# Try extracting from JSON body if not found yet
if not token and request.content_type == "application/json":
try:
body = json.loads(request.body)
token = body.get("captcha_token")
except (json.JSONDecodeError, TypeError):
pass # Malformed or empty JSON
if not token or not verify_token(token):
return response(request) if response else JsonResponse({
"error": "CAPTCHA",

View File

@ -1,11 +1,6 @@
// ==================== HOPTCHA CLIENT ====================
(function(window, $) {
if (!$) {
console.error("Hoptcha requires jQuery. Please include it before this script.");
return;
}
// ==================== HOPTCHA CLIENT (Vanilla JS) ====================
(function (window) {
/**
* Handle messages from the iframe.
*/
@ -24,13 +19,16 @@
* @param {string} url - The Hoptcha URL endpoint.
*/
window.renderCaptchaStep = function (url) {
$('#hoptcha-container').html(`
const container = document.getElementById("hoptcha-container");
if (container) {
container.innerHTML = `
<iframe
id="captcha-iframe"
src=${url}
src="${url}"
style="width: 100%; height: 250px; border: none; border-radius: 12px;"
></iframe>
`);
`;
}
};
/**
@ -39,13 +37,30 @@
* @param {object} payload - Payload to send, must include `captcha_token`.
* @param {function} onSuccess - Success handler.
* @param {function} [onError] - Error fallback.
* @param {function} [onCaptcha] - Optional custom render callback.
*/
window.hoptchaPost = function (url, payload, onSuccess, onError, onCaptcha) {
$.post(url, payload, function(data) {
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload)
})
.then(response => {
if (!response.ok) {
return response.json().then(data => {
throw { status: response.status, data };
});
}
return response.json();
})
.then(data => {
if (onSuccess) onSuccess(data);
}).fail(xhr => {
const error = xhr.responseJSON ? xhr.responseJSON.error : 'Something went wrong.';
const captcha_url = xhr.responseJSON?.url;
})
.catch(err => {
const error = err.data?.error || 'Something went wrong.';
const captcha_url = err.data?.url;
if (!captcha_url) {
if (onError) onError(error);
@ -63,5 +78,4 @@
render(captcha_url);
});
};
})(window, window.jQuery);
})(window);

View File

@ -4,7 +4,6 @@
<head>
<meta charset="UTF-8">
<title>Hoptcha Demo</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
<style>
body {
@ -73,14 +72,23 @@
<script>
function closeCaptcha() {
$('#custom-captcha-modal').hide();
const modal = document.getElementById("custom-captcha-modal");
if (modal) modal.style.display = "none";
}
$("#demo-form").submit(function(e) {
const demoForm = document.getElementById("demo-form");
if (demoForm) {
demoForm.addEventListener("submit", function (e) {
e.preventDefault();
const formData = $(this).serializeArray();
let payload = {};
formData.forEach(item => payload[item.name] = item.value);
const formData = new FormData(demoForm);
const payload = {};
for (const [key, value] of formData.entries()) {
payload[key] = value;
}
payload["captcha_token"] = "init";
hoptchaPost('/submit/', payload, function (data) {
@ -89,10 +97,14 @@
}, function (error) {
alert(error);
}, function renderCustomCaptcha(url) {
$('#custom-captcha-frame').attr('src', url);
$('#custom-captcha-modal').show();
const frame = document.getElementById("custom-captcha-frame");
const modal = document.getElementById("custom-captcha-modal");
if (frame) frame.src = url;
if (modal) modal.style.display = "block";
});
});
}
</script>
</body>
</html>

View File

@ -22,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import json
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
@ -30,10 +32,16 @@ from django_hoptcha.decorators import hoptcha_protected
@csrf_exempt
@hoptcha_protected(threshold=3, timeout=300)
def protected_form_submit(request):
name = request.POST.get("name")
try:
data = json.loads(request.body)
name = data.get('name', '')
if not name:
return JsonResponse({"error": "Name is required."}, status=400)
return JsonResponse({"success": f"Hello, {name}!"})
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
def form_index(request):
return render(request, "templates/form.html")