# contact-forms/trunk/assets/js/frontend/cap.js

Contact Forms by Cimatti, version trunk. 138 lines.

- Page: https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/frontend/cap.js
- Raw: https://pluginprobe.com/plugins/contact-forms/trunk/raw/assets/js/frontend/cap.js
- Modified: 2026-08-21T08:39:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/frontend/cap.js#L10-L20`.

```javascript
/**
 * 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);

```
