New Captcha Package added

This commit is contained in:
2026-07-25 13:38:18 +05:30
parent d0aded0392
commit 8b728a3e5d
157 changed files with 12092 additions and 1913 deletions
+5
View File
@@ -0,0 +1,5 @@
# Bundled English audio clips
These PCM WAV clips were generated locally with eSpeak for the initial English CAPTCHA vocabulary. They cover digits, letters, and calculation words. Applications may replace the renderer or asset directory to use recorded voices, additional languages, or an external text-to-speech service.
The package does not transmit text to an external speech provider. Keep an accessible non-audio alternative available and rate-limit audio playback endpoints.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+878
View File
@@ -0,0 +1,878 @@
(function () {
"use strict";
var RUNTIME_KEY = "__wrnexusCaptchaRuntime";
var existingRuntime = window[RUNTIME_KEY];
if (existingRuntime && typeof existingRuntime.scan === "function") {
existingRuntime.scan(document);
return;
}
var states = new WeakMap();
var scriptPromises = new Map();
var instanceCounter = 0;
function bool(value, fallback) {
if (value === undefined || value === null || value === "") return fallback;
return value === true || value === "true" || value === "1";
}
function numberInRange(value, fallback, minimum, maximum) {
var parsed = Number(value);
if (!Number.isFinite(parsed)) return fallback;
return Math.max(minimum, Math.min(maximum, Math.round(parsed)));
}
function normalizeSize(value, compact) {
if (compact) return "compact";
var normalized = String(value || "normal").trim().toLowerCase();
if (normalized === "compact" || normalized === "small" || normalized === "sm") return "compact";
if (normalized === "big" || normalized === "large" || normalized === "lg") return "big";
return "normal";
}
function commaList(value) {
return String(value || "")
.split(",")
.map(function (item) { return item.trim().toLowerCase(); })
.filter(Boolean);
}
function text(root, selector, value) {
var element = root.querySelector(selector);
if (element) element.textContent = value == null ? "" : String(value);
}
function show(element, visible) {
if (!element) return;
element.hidden = !visible;
}
function setBusy(root, busy) {
root.setAttribute("aria-busy", busy ? "true" : "false");
}
function config(root) {
var data = root.dataset;
var responseField = data.captchaResponseField || data.captchaName || "wrn-captcha-response";
var compact = bool(data.captchaCompact, false);
var size = normalizeSize(data.captchaSize, compact);
return {
provider: data.captchaProvider || "self-hosted",
siteKey: data.captchaSiteKey || "",
type: data.captchaType || "alphanumeric",
action: data.captchaAction || "form-submit",
presentation: data.captchaPresentation || "visual",
difficulty: data.captchaDifficulty || "normal",
disturbance: numberInRange(data.captchaDisturbance, 50, 25, 75),
imageStyle: data.captchaImageStyle || "random",
allowedStyles: commaList(data.captchaAllowedStyles),
excludedStyles: commaList(data.captchaExcludedStyles),
randomizeStyle: bool(data.captchaRandomizeStyle, false),
locale: data.captchaLocale || "en",
size: size,
endpoint: data.captchaEndpoint || "/__wrnexus/captcha/challenge",
verifyEndpoint: data.captchaVerifyEndpoint || "/__wrnexus/captcha/verify",
responseField: responseField,
autoLoad: bool(data.captchaAutoLoad, true),
autoVerify: bool(data.captchaAutoVerify, false),
showVerify: bool(data.captchaShowVerify, true),
showRefresh: bool(data.captchaShowRefresh, true),
showAudio: bool(data.captchaShowAudio, true),
showListen: bool(data.captchaShowListen, true),
showStatus: bool(data.captchaShowStatus, true),
disabled: bool(data.captchaDisabled, false),
required: bool(data.captchaRequired, true),
compact: compact,
requiredMessage: data.captchaRequiredMessage || "Please complete the security check.",
incorrectMessage: data.captchaIncorrectMessage || "That answer was not correct. Try again.",
expiredMessage: data.captchaExpiredMessage || "This challenge expired. Load a new one.",
networkMessage: data.captchaNetworkMessage || "The verification service is unavailable. Try again.",
};
}
function eventDetail(state, extra) {
var challenge = state.challenge;
return {
component: "Captcha",
provider: state.config.provider,
type: challenge && challenge.type ? challenge.type : state.config.type,
action: state.config.action,
disturbance: state.config.disturbance,
imageStyle: challenge && challenge.metadata && challenge.metadata.imageStyle
? challenge.metadata.imageStyle
: state.config.imageStyle,
requestedImageStyle: challenge && challenge.metadata && challenge.metadata.requestedImageStyle
? challenge.metadata.requestedImageStyle
: state.config.imageStyle,
imageStylePool: challenge && challenge.metadata && challenge.metadata.imageStylePool
? challenge.metadata.imageStylePool
: state.config.allowedStyles,
size: state.config.size,
status: state.status,
challengeId: challenge && challenge.id ? challenge.id : "",
responseToken: state.responseToken,
expiresAt: challenge && challenge.expiresAt ? challenge.expiresAt : null,
extra: extra || null,
};
}
function emit(state, name, extra) {
state.root.dispatchEvent(
new CustomEvent(name, {
bubbles: true,
detail: eventDetail(state, extra),
}),
);
}
function setResponseToken(state, token) {
state.responseToken = token || "";
var input = state.root.querySelector("[data-captcha-response]");
if (input) {
input.name = state.config.responseField;
input.value = state.responseToken;
}
}
function isNotRobot(state) {
return Boolean(
(state.challenge && state.challenge.type === "not-robot") ||
(!state.challenge && state.config.type === "not-robot"),
);
}
function updateNotRobotState(state) {
var panel = state.root.querySelector("[data-captcha-not-robot]");
var button = state.root.querySelector("[data-captcha-not-robot-button]");
var empty = state.root.querySelector("[data-captcha-not-robot-empty]");
var spinner = state.root.querySelector("[data-captcha-not-robot-spinner]");
var check = state.root.querySelector("[data-captcha-not-robot-check]");
var control = state.root.querySelector("[data-captcha-not-robot-control]");
var label = state.root.querySelector("[data-captcha-not-robot-label]");
var active = isNotRobot(state);
var pending = state.notRobotPending || state.status === "verifying";
var verified = state.status === "verified";
show(panel, active && state.status !== "loading" && state.status !== "idle");
if (!active) return;
if (button) {
button.disabled = state.config.disabled || pending || verified || state.status === "expired";
button.setAttribute("aria-pressed", verified ? "true" : "false");
}
show(empty, !pending && !verified);
show(spinner, pending && !verified);
show(check, verified);
if (control) control.dataset.verified = verified ? "true" : "false";
if (label) {
label.textContent = verified ? "Verified" : pending ? "Checking…" : "I'm not a robot";
}
}
function setStatus(state, status, message) {
state.status = status;
state.root.dataset.captchaStatus = status;
setBusy(state.root, status === "loading" || status === "verifying");
var loading = state.root.querySelector("[data-captcha-loading]");
var challenge = state.root.querySelector("[data-captcha-challenge]");
var success = state.root.querySelector("[data-captcha-success]");
var error = state.root.querySelector("[data-captcha-error]");
var badge = state.root.querySelector("[data-captcha-verified-badge]");
var verifyButton = state.root.querySelector("[data-captcha-verify]");
var verifyIcon = state.root.querySelector("[data-captcha-verify-icon]");
var verifySpinner = state.root.querySelector("[data-captcha-verify-spinner]");
var verifyLabel = state.root.querySelector("[data-captcha-verify-label]");
var answer = state.root.querySelector("[data-captcha-answer]");
show(loading, status === "loading");
show(challenge, status !== "loading" && status !== "idle" && state.config.provider !== "turnstile" && state.config.provider !== "recaptcha" && state.config.provider !== "hcaptcha");
show(badge, status === "verified");
if (state.config.showStatus) {
show(success, status === "verified");
show(error, Boolean(message) && status !== "verified");
} else {
show(success, false);
show(error, false);
}
if (message && status === "verified") text(state.root, "[data-captcha-success-message]", message);
if (message && status !== "verified") text(state.root, "[data-captcha-error-message]", message);
if (answer) {
answer.disabled = state.config.disabled || status === "verified" || status === "expired" || status === "loading" || status === "verifying";
answer.setAttribute("aria-invalid", status === "incorrect" ? "true" : "false");
}
if (verifyButton) {
verifyButton.disabled = state.config.disabled || status === "loading" || status === "verifying" || status === "verified" || status === "expired";
}
show(verifyIcon, status !== "verifying");
show(verifySpinner, status === "verifying");
if (verifyLabel) verifyLabel.textContent = status === "verifying" ? "Verifying…" : "Verify";
updateNotRobotState(state);
updateControls(state);
}
function clearTimer(state) {
if (state.timer) {
window.clearInterval(state.timer);
state.timer = null;
}
}
function updateCountdown(state) {
var countdown = state.root.querySelector("[data-captcha-countdown]");
if (!countdown || !state.challenge || !state.challenge.expiresAt) {
show(countdown, false);
return;
}
var remaining = Math.max(0, Math.ceil((Number(state.challenge.expiresAt) - Date.now()) / 1000));
countdown.textContent = remaining + "s";
show(countdown, remaining > 0 && state.status !== "verified");
if (remaining <= 0 && state.status !== "verified" && state.status !== "expired") {
clearTimer(state);
setResponseToken(state, "");
setStatus(state, "expired", state.config.expiredMessage);
emit(state, "expired");
}
}
function startTimer(state) {
clearTimer(state);
if (!state.challenge || !state.challenge.expiresAt) return;
updateCountdown(state);
state.timer = window.setInterval(function () {
updateCountdown(state);
}, 1000);
}
function resetUi(state) {
if (state.audioPlayer) {
state.audioPlayer.pause();
state.audioPlayer.removeAttribute("src");
state.audioPlayer.load();
state.audioPlayer = null;
}
if (state.notRobotTimer) {
window.clearTimeout(state.notRobotTimer);
state.notRobotTimer = null;
}
state.notRobotPending = false;
state.challengeLoadedAt = 0;
state.answer = "";
state.selections = [];
state.challenge = null;
setResponseToken(state, "");
var answer = state.root.querySelector("[data-captcha-answer]");
var items = state.root.querySelector("[data-captcha-items]");
var imageWrap = state.root.querySelector("[data-captcha-image-wrap]");
var providerMount = state.root.querySelector("[data-captcha-provider-mount]");
var honeypot = state.root.querySelector("[data-captcha-honeypot]");
var notRobot = state.root.querySelector("[data-captcha-not-robot]");
if (answer) answer.value = "";
if (items) items.replaceChildren();
show(items, false);
show(imageWrap, false);
show(notRobot, false);
if (honeypot) {
honeypot.value = "";
honeypot.name = "";
}
if (providerMount && state.config.provider !== "turnstile" && state.config.provider !== "recaptcha" && state.config.provider !== "hcaptcha") {
providerMount.replaceChildren();
show(providerMount, false);
}
}
function updateControls(state) {
var challenge = state.challenge;
var audio = state.root.querySelector("[data-captcha-audio]");
var audioAlternative = state.root.querySelector("[data-captcha-audio-alternative]");
var refresh = state.root.querySelector("[data-captcha-refresh]");
var verify = state.root.querySelector("[data-captcha-verify]");
var footer = state.root.querySelector("[data-captcha-footer]");
var isExternal = state.config.provider === "turnstile" || state.config.provider === "recaptcha" || state.config.provider === "hcaptcha";
var notRobot = isNotRobot(state);
var locked = state.config.disabled || state.status === "loading" || state.status === "verifying" || state.notRobotPending;
show(footer, !notRobot);
show(audio, !notRobot && state.config.showAudio && state.config.showListen && Boolean(challenge && challenge.audioUrl) && state.status !== "verified");
show(audioAlternative, !notRobot && state.config.showAudio && Boolean(challenge && challenge.type === "image") && state.status !== "verified");
show(refresh, !notRobot && state.config.showRefresh && state.status !== "verified");
show(verify, !notRobot && state.config.showVerify && !isExternal);
if (audio) audio.disabled = locked;
if (audioAlternative) audioAlternative.disabled = locked;
if (refresh) refresh.disabled = locked;
if (verify) verify.disabled = locked || state.status === "verified" || state.status === "expired";
}
function renderItems(state, challenge) {
var container = state.root.querySelector("[data-captcha-items]");
var template = state.root.querySelector("[data-captcha-item-template]");
if (!container || !template || !Array.isArray(challenge.items) || challenge.items.length === 0) {
show(container, false);
return;
}
container.replaceChildren();
container.setAttribute("aria-label", challenge.prompt || "Select matching images");
challenge.items.forEach(function (item, index) {
var fragment = template.content.cloneNode(true);
var button = fragment.querySelector("button");
var image = fragment.querySelector("[data-captcha-item-image]");
if (!button || !image) return;
button.dataset.captchaItemId = item.id;
button.setAttribute("aria-label", item.alt || "Challenge tile " + (index + 1));
image.src = item.image;
image.alt = "";
button.addEventListener("click", function () {
toggleItem(state, item.id, button);
});
container.appendChild(fragment);
});
show(container, true);
}
function toggleItem(state, itemId, button) {
if (state.config.disabled || state.status !== "ready") return;
var index = state.selections.indexOf(itemId);
var selected = index >= 0;
if (selected) {
state.selections.splice(index, 1);
} else {
var maximum = Number(state.challenge && state.challenge.maxSelections ? state.challenge.maxSelections : 0);
if (maximum > 0 && state.selections.length >= maximum) {
var removed = state.selections.shift();
var previousButton = state.root.querySelector('[data-captcha-item-id="' + CSS.escape(removed) + '"]');
if (previousButton) {
previousButton.setAttribute("aria-pressed", "false");
show(previousButton.querySelector("[data-captcha-item-check]"), false);
}
}
state.selections.push(itemId);
}
var nowSelected = state.selections.indexOf(itemId) >= 0;
button.setAttribute("aria-pressed", nowSelected ? "true" : "false");
show(button.querySelector("[data-captcha-item-check]"), nowSelected);
emit(state, "input", { selections: state.selections.slice() });
var minimum = Number(state.challenge && state.challenge.minSelections ? state.challenge.minSelections : 0);
if (state.config.autoVerify && minimum > 0 && state.selections.length >= minimum) verify(state);
}
function renderChallenge(state, challenge) {
state.challenge = challenge;
state.challengeLoadedAt = Date.now();
state.notRobotPending = false;
state.answer = "";
state.selections = [];
var notRobot = challenge.type === "not-robot";
state.root.dataset.captchaType = challenge.type || state.config.type;
state.root.dataset.captchaResolvedImageStyle = challenge.metadata && challenge.metadata.imageStyle
? String(challenge.metadata.imageStyle)
: state.config.imageStyle;
text(state.root, "[data-captcha-prompt]", challenge.prompt || "Complete the security challenge.");
var promptRow = state.root.querySelector("[data-captcha-prompt-row]");
var notRobotPanel = state.root.querySelector("[data-captcha-not-robot]");
show(promptRow, !notRobot);
show(notRobotPanel, notRobot);
var imageWrap = state.root.querySelector("[data-captcha-image-wrap]");
var image = state.root.querySelector("[data-captcha-image]");
if (!notRobot && challenge.image && image) {
image.src = challenge.image;
show(imageWrap, true);
} else {
if (image) image.removeAttribute("src");
show(imageWrap, false);
}
if (notRobot) {
var items = state.root.querySelector("[data-captcha-items]");
if (items) items.replaceChildren();
show(items, false);
} else {
renderItems(state, challenge);
}
var answerWrap = state.root.querySelector("[data-captcha-answer-wrap]");
var answer = state.root.querySelector("[data-captcha-answer]");
var needsAnswer = !notRobot && !challenge.items && challenge.inputMode !== "none";
show(answerWrap, needsAnswer);
if (answer) {
answer.value = "";
answer.inputMode = challenge.inputMode === "numeric" ? "numeric" : "text";
answer.setAttribute("aria-invalid", "false");
}
var honeypot = state.root.querySelector("[data-captcha-honeypot]");
if (honeypot) honeypot.name = challenge.honeypotField || "";
setStatus(state, "ready", "");
startTimer(state);
emit(state, "challenge", challenge);
emit(state, "ready");
if (state.config.autoVerify && !notRobot && challenge.inputMode === "none" && !challenge.items) {
window.setTimeout(function () {
verify(state);
}, 0);
}
}
async function createChallenge(state, requestedPresentation) {
if (state.config.disabled) return;
if (state.config.provider === "turnstile" || state.config.provider === "recaptcha" || state.config.provider === "hcaptcha") {
await mountExternal(state);
return;
}
clearTimer(state);
resetUi(state);
setStatus(state, "loading", "");
try {
var response = await fetch(state.config.endpoint, {
method: "POST",
credentials: "same-origin",
headers: {
"content-type": "application/json",
accept: "application/json",
},
body: JSON.stringify({
siteKey: state.config.siteKey,
action: state.config.action,
type: state.config.type,
presentation: requestedPresentation || state.config.presentation,
difficulty: state.config.difficulty,
disturbance: state.config.disturbance,
imageStyle: state.config.imageStyle,
allowedStyles: state.config.allowedStyles,
excludedStyles: state.config.excludedStyles,
randomizeStyle: state.config.randomizeStyle,
locale: state.config.locale,
responseField: state.config.responseField,
}),
});
var result = await response.json().catch(function () {
return null;
});
if (!response.ok || !result || !result.id) {
throw new Error(result && result.message ? result.message : "Challenge request failed");
}
renderChallenge(state, result);
} catch (error) {
setStatus(state, "network-error", state.config.networkMessage);
emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
}
}
async function verify(state) {
state.notRobotPending = false;
if (state.config.disabled || state.status === "loading" || state.status === "verifying" || state.status === "expired" || state.status === "verified") {
updateNotRobotState(state);
updateControls(state);
return;
}
if (!state.challenge) {
await createChallenge(state);
return;
}
var answerInput = state.root.querySelector("[data-captcha-answer]");
var honeypotInput = state.root.querySelector("[data-captcha-honeypot]");
state.answer = answerInput ? answerInput.value : "";
setStatus(state, "verifying", "");
emit(state, "verify");
try {
var response = await fetch(state.challenge.verifyUrl || state.config.verifyEndpoint, {
method: "POST",
credentials: "same-origin",
headers: {
"content-type": "application/json",
accept: "application/json",
},
body: JSON.stringify({
challengeId: state.challenge.id,
action: state.config.action,
answer: state.answer,
selections: state.selections,
honeypot: honeypotInput ? honeypotInput.value : "",
timingToken: state.challenge.timingToken || "",
}),
});
var result = await response.json().catch(function () {
return null;
});
if (!response.ok || !result || !result.success) {
setResponseToken(state, "");
var expired = Boolean(result && result.code === "expired");
setStatus(
state,
expired ? "expired" : "incorrect",
expired
? state.config.expiredMessage
: result && result.message
? result.message
: state.config.incorrectMessage,
);
emit(state, "failure", result || null);
return;
}
clearTimer(state);
setResponseToken(state, result.responseToken || "");
setStatus(state, "verified", "Verification completed.");
emit(state, "success", result);
} catch (error) {
setStatus(state, "network-error", state.config.networkMessage);
emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
}
}
function verifyNotRobot(state) {
if (state.config.disabled || state.notRobotPending || state.status !== "ready") return;
if (!state.challenge || state.challenge.type !== "not-robot") {
createChallenge(state);
return;
}
var configuredMinimum = Number(
state.challenge.metadata && state.challenge.metadata.minCompletionMs !== undefined
? state.challenge.metadata.minCompletionMs
: 800,
);
var minimum = Number.isFinite(configuredMinimum) ? Math.max(0, configuredMinimum) : 800;
var elapsed = Math.max(0, Date.now() - state.challengeLoadedAt);
var remaining = Math.max(0, minimum - elapsed);
state.notRobotPending = true;
updateNotRobotState(state);
updateControls(state);
emit(state, "input", { checked: true });
var complete = function () {
state.notRobotTimer = null;
state.notRobotPending = false;
verify(state);
};
if (remaining > 0) state.notRobotTimer = window.setTimeout(complete, remaining);
else complete();
}
function providerDefinition(provider) {
if (provider === "turnstile") {
return {
url: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
globalName: "turnstile",
};
}
if (provider === "recaptcha") {
return {
url: "https://www.google.com/recaptcha/api.js?render=explicit",
globalName: "grecaptcha",
};
}
if (provider === "hcaptcha") {
return {
url: "https://js.hcaptcha.com/1/api.js?render=explicit",
globalName: "hcaptcha",
};
}
return null;
}
function loadProviderScript(definition) {
if (window[definition.globalName]) return Promise.resolve(window[definition.globalName]);
if (scriptPromises.has(definition.url)) return scriptPromises.get(definition.url);
var promise = new Promise(function (resolve, reject) {
var script = document.querySelector('script[src="' + definition.url + '"]');
if (!script) {
script = document.createElement("script");
script.src = definition.url;
script.async = true;
script.defer = true;
document.head.appendChild(script);
}
script.addEventListener(
"load",
function () {
if (window[definition.globalName]) resolve(window[definition.globalName]);
else reject(new Error("CAPTCHA provider did not initialize"));
},
{ once: true },
);
script.addEventListener("error", function () { reject(new Error("CAPTCHA provider script failed")); }, { once: true });
});
scriptPromises.set(definition.url, promise);
return promise;
}
async function mountExternal(state) {
var definition = providerDefinition(state.config.provider);
if (!definition) return;
if (!state.config.siteKey) {
setStatus(state, "provider-error", "A public site key is required.");
return;
}
var mount = state.root.querySelector("[data-captcha-provider-mount]");
if (!mount) return;
show(mount, true);
setStatus(state, "loading", "");
try {
var api = await loadProviderScript(definition);
if (state.externalWidgetId !== null && typeof api.reset === "function") {
setResponseToken(state, "");
api.reset(state.externalWidgetId);
setStatus(state, "ready", "");
emit(state, "ready");
return;
}
mount.replaceChildren();
state.externalApi = api;
state.externalWidgetId = api.render(mount, {
sitekey: state.config.siteKey,
theme: "auto",
size: state.config.size === "compact" ? "compact" : state.config.provider === "turnstile" ? "flexible" : "normal",
action: state.config.action,
callback: function (token) {
setResponseToken(state, token);
setStatus(state, "verified", "Verification completed.");
emit(state, "success");
},
"expired-callback": function () {
setResponseToken(state, "");
setStatus(state, "expired", state.config.expiredMessage);
emit(state, "expired");
},
"error-callback": function (providerError) {
setResponseToken(state, "");
setStatus(state, "provider-error", state.config.networkMessage);
emit(state, "error", { error: providerError || "provider-error" });
},
});
setStatus(state, "ready", "");
emit(state, "ready");
} catch (error) {
setStatus(state, "provider-error", state.config.networkMessage);
emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
}
}
function playAudio(state) {
if (!state.challenge || !state.challenge.audioUrl || state.config.disabled) return;
if (state.audioPlayer) {
state.audioPlayer.pause();
state.audioPlayer.currentTime = 0;
}
var audio = new Audio();
state.audioPlayer = audio;
audio.preload = "auto";
audio.volume = 1;
audio.src = new URL(state.challenge.audioUrl, window.location.href).href;
audio.addEventListener("ended", function () {
if (state.audioPlayer === audio) state.audioPlayer = null;
emit(state, "audioEnd");
emit(state, "audio-end");
}, { once: true });
audio.addEventListener("error", function () {
if (state.audioPlayer === audio) state.audioPlayer = null;
var mediaError = audio.error;
setStatus(state, "network-error", "The audio challenge could not be played. Load a new challenge and try again.");
emit(state, "error", {
code: "audio-playback-failed",
audioUrl: audio.src,
mediaErrorCode: mediaError ? mediaError.code : null,
});
}, { once: true });
audio.load();
audio.play().then(function () {
emit(state, "audioStart");
emit(state, "audio-start");
}).catch(function (error) {
if (state.audioPlayer === audio) state.audioPlayer = null;
setStatus(state, "network-error", "The audio challenge could not be played. Check the audio endpoint and try again.");
emit(state, "error", {
code: "audio-playback-rejected",
audioUrl: audio.src,
error: error instanceof Error ? error.message : String(error),
});
});
}
function onFormSubmit(state, event) {
if (!state.config.required || state.responseToken) return;
event.preventDefault();
event.stopImmediatePropagation();
setStatus(state, "incorrect", state.config.requiredMessage);
emit(state, "failure", { code: "missing-input" });
var target = state.root.querySelector("[data-captcha-answer]") || state.root.querySelector("[data-captcha-not-robot-button]") || state.root.querySelector("[data-captcha-verify]");
if (target && typeof target.focus === "function") target.focus();
}
function initialize(root) {
if (!(root instanceof HTMLElement) || states.has(root)) return;
if (!root.id) {
instanceCounter += 1;
root.id = "wrn-captcha-" + instanceCounter;
}
var state = {
root: root,
config: config(root),
challenge: null,
answer: "",
selections: [],
responseToken: "",
status: "idle",
timer: null,
notRobotTimer: null,
notRobotPending: false,
challengeLoadedAt: 0,
externalApi: null,
externalWidgetId: null,
audioPlayer: null,
form: root.closest("form"),
};
states.set(root, state);
root.dataset.captchaSize = state.config.size;
var response = root.querySelector("[data-captcha-response]");
if (response) response.name = state.config.responseField;
var answer = root.querySelector("[data-captcha-answer]");
var verifyButton = root.querySelector("[data-captcha-verify]");
var refreshButton = root.querySelector("[data-captcha-refresh]");
var audioButton = root.querySelector("[data-captcha-audio]");
var audioAlternative = root.querySelector("[data-captcha-audio-alternative]");
var notRobotButton = root.querySelector("[data-captcha-not-robot-button]");
if (answer) {
answer.addEventListener("input", function () {
state.answer = answer.value;
emit(state, "input", { answerLength: state.answer.length });
});
answer.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
event.preventDefault();
verify(state);
}
});
}
if (verifyButton) verifyButton.addEventListener("click", function () { verify(state); });
if (notRobotButton) notRobotButton.addEventListener("click", function () { verifyNotRobot(state); });
if (refreshButton) refreshButton.addEventListener("click", function () {
emit(state, "refresh");
createChallenge(state, state.config.presentation);
});
if (audioButton) audioButton.addEventListener("click", function () { playAudio(state); });
if (audioAlternative) audioAlternative.addEventListener("click", function () {
emit(state, "refresh", { presentation: "audio" });
createChallenge(state, "audio");
});
root.addEventListener("captcha-reset", function () {
emit(state, "refresh");
createChallenge(state, state.config.presentation);
});
if (state.form) {
state.form.addEventListener("submit", function (event) {
onFormSubmit(state, event);
}, true);
state.form.addEventListener("wire:success", function () {
createChallenge(state, state.config.presentation);
});
}
updateNotRobotState(state);
updateControls(state);
if (state.config.disabled) setStatus(state, "disabled", "");
else if (state.config.autoLoad) createChallenge(state, state.config.presentation);
}
function scan(scope) {
var host = scope || document;
if (host instanceof Element && host.matches("[data-wrn-captcha]")) initialize(host);
host.querySelectorAll("[data-wrn-captcha]").forEach(initialize);
}
var runtime = {
scan: scan,
reset: function (element) {
var root = typeof element === "string" ? document.querySelector(element) : element;
var state = root ? states.get(root) : null;
if (state) createChallenge(state, state.config.presentation);
},
verify: function (element) {
var root = typeof element === "string" ? document.querySelector(element) : element;
var state = root ? states.get(root) : null;
return state ? verify(state) : Promise.resolve();
},
};
window[RUNTIME_KEY] = runtime;
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () { scan(document); }, { once: true });
} else {
scan(document);
}
new MutationObserver(function (records) {
records.forEach(function (record) {
record.addedNodes.forEach(function (node) {
if (node.nodeType === 1) scan(node);
});
});
}).observe(document.documentElement, { childList: true, subtree: true });
})();