PluginProbe
Contact Forms by Cimatti / 2.3.5
Contact Forms by Cimatti v2.3.5
2.3.6 2.3.5 2.3.0 2.2.32 2.2.4 2.2.0 2.1.2 2.1.1 trunk 1.0 1.1 1.2 1.2.1 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 All 62 releases
contact-forms / assets / js / frontend / cap.js

cap.js in Contact Forms by Cimatti 2.3.5, at assets/js/frontend/cap.js

138 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Cap (capjs.js.org) widget loader for Contact Forms.
3 *
4 * Each cap field registers itself through the global queue
5 * `accuaformCapQueue` (so registration works whether the field markup is
6 * parsed before or after this file loads). The cap-widget script is loaded
7 * from the site owner's own Cap Standalone instance - no third-party request
8 * is ever made.
9 *
10 * The widget solves a proof-of-work challenge when the visitor clicks it and
11 * emits a single-use token in its `solve` event. The token is mirrored into
12 * the plugin's own hidden input (name accua-forms-cap-response, id = field
13 * id) so server-side validation and the error summary anchoring work exactly
14 * like the other captcha fields. Server-side validation is fail-closed: an
15 * empty or stale token is rejected by the Cap siteverify endpoint.
16 *
17 * Tokens are single-use, so after every AJAX round trip (success or
18 * validation error) the handler generated by AccuaForm.php calls
19 * accuaformCap.reset() so a retry solves a fresh challenge.
20 */
21
22 (function ($) {
23 var registered = {}; // inputId -> {endpoint, widgetUrl, wasmUrl, i18n}
24 var widgets = {}; // inputId -> cap-widget DOM element
25 var widgetPromise = null;
26
27 // Resolves when the cap-widget custom element is defined (never rejects:
28 // on timeout the container simply stays empty and the server rejects the
29 // token-less submission).
30 function loadWidget(cfg) {
31 if (widgetPromise) {
32 return widgetPromise;
33 }
34 widgetPromise = new Promise(function (resolve) {
35 if (window.customElements && window.customElements.get('cap-widget')) {
36 resolve();
37 return;
38 }
39 // Point the WASM solver at the same Cap instance before the widget
40 // script initializes (its default is a public CDN).
41 if (cfg.wasmUrl && !window.CAP_CUSTOM_WASM_URL) {
42 window.CAP_CUSTOM_WASM_URL = cfg.wasmUrl;
43 }
44 if (!document.querySelector('script[data-accua-cap-widget]')) {
45 var script = document.createElement('script');
46 script.src = cfg.widgetUrl;
47 script.async = true;
48 script.setAttribute('data-accua-cap-widget', '1');
49 document.head.appendChild(script);
50 }
51 var waited = 0;
52 var poll = setInterval(function () {
53 waited += 200;
54 if ((window.customElements && window.customElements.get('cap-widget')) || waited >= 20000) {
55 clearInterval(poll);
56 resolve();
57 }
58 }, 200);
59 });
60 return widgetPromise;
61 }
62
63 function createWidget(inputId) {
64 var cfg = registered[inputId];
65 var input = document.getElementById(inputId);
66 var container = document.getElementById(inputId + '-widget');
67 if (!cfg || !input || !container || container.querySelector('cap-widget')) {
68 return;
69 }
70 if (!(window.customElements && window.customElements.get('cap-widget'))) {
71 return; // loadWidget() timed out
72 }
73
74 var widget = document.createElement('cap-widget');
75 widget.setAttribute('data-cap-api-endpoint', cfg.endpoint);
76 if (cfg.i18n) {
77 for (var key in cfg.i18n) {
78 if (Object.prototype.hasOwnProperty.call(cfg.i18n, key) && cfg.i18n[key]) {
79 widget.setAttribute('data-cap-i18n-' + key, cfg.i18n[key]);
80 }
81 }
82 }
83
84 widget.addEventListener('solve', function (e) {
85 input.value = (e && e.detail && e.detail.token) ? e.detail.token : '';
86 });
87 widget.addEventListener('reset', function () {
88 input.value = '';
89 });
90 widget.addEventListener('error', function () {
91 input.value = '';
92 });
93
94 container.appendChild(widget);
95 widgets[inputId] = widget;
96 }
97
98 // Public API used by the AJAX submit handler generated in AccuaForm.php:
99 // after every AJAX response the used (single-use) tokens are cleared and
100 // the widgets reset so a retry solves a fresh challenge.
101 window.accuaformCap = {
102 reset: function (formEl) {
103 $(formEl).find('input.accua_forms_cap_input').each(function () {
104 this.value = '';
105 var widget = widgets[this.id];
106 if (widget && typeof widget.reset === 'function') {
107 try {
108 widget.reset();
109 } catch (err) {
110 // ignore - widget may not be fully initialized yet
111 }
112 }
113 });
114 }
115 };
116
117 // item = [inputId, {endpoint, widgetUrl, wasmUrl, i18n}, language]
118 function register(item) {
119 var id = item[0];
120 if (registered[id]) {
121 return;
122 }
123 registered[id] = item[1];
124 loadWidget(item[1]).then(function () {
125 createWidget(id);
126 });
127 }
128
129 var queue = window.accuaformCapQueue = window.accuaformCapQueue || [];
130 for (var i = 0; i < queue.length; i++) {
131 register(queue[i]);
132 }
133 queue.push = function (item) {
134 register(item);
135 return queue.length;
136 };
137 })(jQuery);
138