/** * Cap (capjs.js.org) widget loader for Contact Forms. * * Each cap field registers itself through the global queue * `accuaformCapQueue` (so registration works whether the field markup is * parsed before or after this file loads). The cap-widget script is loaded * from the site owner's own Cap Standalone instance - no third-party request * is ever made. * * The widget solves a proof-of-work challenge when the visitor clicks it and * emits a single-use token in its `solve` event. The token is mirrored into * the plugin's own hidden input (name accua-forms-cap-response, id = field * id) so server-side validation and the error summary anchoring work exactly * like the other captcha fields. Server-side validation is fail-closed: an * empty or stale token is rejected by the Cap siteverify endpoint. * * Tokens are single-use, so after every AJAX round trip (success or * validation error) the handler generated by AccuaForm.php calls * accuaformCap.reset() so a retry solves a fresh challenge. */ (function ($) { var registered = {}; // inputId -> {endpoint, widgetUrl, wasmUrl, i18n} var widgets = {}; // inputId -> cap-widget DOM element var widgetPromise = null; // Resolves when the cap-widget custom element is defined (never rejects: // on timeout the container simply stays empty and the server rejects the // token-less submission). function loadWidget(cfg) { if (widgetPromise) { return widgetPromise; } widgetPromise = new Promise(function (resolve) { if (window.customElements && window.customElements.get('cap-widget')) { resolve(); return; } // Point the WASM solver at the same Cap instance before the widget // script initializes (its default is a public CDN). if (cfg.wasmUrl && !window.CAP_CUSTOM_WASM_URL) { window.CAP_CUSTOM_WASM_URL = cfg.wasmUrl; } if (!document.querySelector('script[data-accua-cap-widget]')) { var script = document.createElement('script'); script.src = cfg.widgetUrl; script.async = true; script.setAttribute('data-accua-cap-widget', '1'); document.head.appendChild(script); } var waited = 0; var poll = setInterval(function () { waited += 200; if ((window.customElements && window.customElements.get('cap-widget')) || waited >= 20000) { clearInterval(poll); resolve(); } }, 200); }); return widgetPromise; } function createWidget(inputId) { var cfg = registered[inputId]; var input = document.getElementById(inputId); var container = document.getElementById(inputId + '-widget'); if (!cfg || !input || !container || container.querySelector('cap-widget')) { return; } if (!(window.customElements && window.customElements.get('cap-widget'))) { return; // loadWidget() timed out } var widget = document.createElement('cap-widget'); widget.setAttribute('data-cap-api-endpoint', cfg.endpoint); if (cfg.i18n) { for (var key in cfg.i18n) { if (Object.prototype.hasOwnProperty.call(cfg.i18n, key) && cfg.i18n[key]) { widget.setAttribute('data-cap-i18n-' + key, cfg.i18n[key]); } } } widget.addEventListener('solve', function (e) { input.value = (e && e.detail && e.detail.token) ? e.detail.token : ''; }); widget.addEventListener('reset', function () { input.value = ''; }); widget.addEventListener('error', function () { input.value = ''; }); container.appendChild(widget); widgets[inputId] = widget; } // Public API used by the AJAX submit handler generated in AccuaForm.php: // after every AJAX response the used (single-use) tokens are cleared and // the widgets reset so a retry solves a fresh challenge. window.accuaformCap = { reset: function (formEl) { $(formEl).find('input.accua_forms_cap_input').each(function () { this.value = ''; var widget = widgets[this.id]; if (widget && typeof widget.reset === 'function') { try { widget.reset(); } catch (err) { // ignore - widget may not be fully initialized yet } } }); } }; // item = [inputId, {endpoint, widgetUrl, wasmUrl, i18n}, language] function register(item) { var id = item[0]; if (registered[id]) { return; } registered[id] = item[1]; loadWidget(item[1]).then(function () { createWidget(id); }); } var queue = window.accuaformCapQueue = window.accuaformCapQueue || []; for (var i = 0; i < queue.length; i++) { register(queue[i]); } queue.push = function (item) { register(item); return queue.length; }; })(jQuery);