PageSourceSearch

https://www.rechtsanwalt-schullerus.com/wp-content/plugins/ommatic…/assets/ommatic-captcha.js?ver=1791113044

js rechtsanwalt-schullerus.com collected 2026-10-07 03:52:51 UTC 6,965 bytes, 235 lines download raw bytes

1/**
2 * OMmatic Captcha — client side.
3 * Cache-safe: this static file only fetches a fresh, server-signed challenge from
4 * a dynamic REST endpoint, solves an invisible proof-of-work, and injects the
5 * token (+ honeypot) into Elementor Pro, WPForms and Contact Form 7 forms. No
6 * third parties, no consent needed.
7 *
8 * The hidden inputs are appended to the <form> itself, so every submit path picks
9 * them up: Elementor serialises the form, WPForms and CF7 build a FormData from
10 * the form element (classic submits post them anyway).
11 */
12(function () {
13	'use strict';
14
15	var cfg = window.__ommaticCaptcha;
16	if (!cfg || !cfg.challengeUrl) {
17		return;
18	}
19
20	var FORM_SELECTOR = 'form.elementor-form, form.wpforms-form, form.wpcf7-form';
21	var solving = false;
22	var token = null; // { cap: string, nonce: string }
23
24	function forms() {
25		return document.querySelectorAll(FORM_SELECTOR);
26	}
27
28	function hasForm() {
29		return forms().length > 0;
30	}
31
32	// --- proof-of-work in a Web Worker (SubtleCrypto; https-only) -------------
33	function solvePow(id, zeros) {
34		return new Promise(function (resolve, reject) {
35			var prefix = '';
36			for (var i = 0; i < zeros; i++) {
37				prefix += '0';
38			}
39			var code =
40				'self.onmessage=async function(e){' +
41				'var id=e.data.id,prefix=e.data.prefix,enc=new TextEncoder();' +
42				'for(var n=0;;n++){' +
43				'var b=await crypto.subtle.digest("SHA-256",enc.encode(id+":"+n));' +
44				'var a=new Uint8Array(b),h="";' +
45				'for(var i=0;i<prefix.length;i++){var v=a[i>>1];var nib=(i&1)?(v&15):(v>>4);' +
46				'if(nib!==0){h="x";break;}}' +
47				'if(h===""){self.postMessage(String(n));return;}}};';
48			try {
49				var url = URL.createObjectURL(new Blob([code], { type: 'text/javascript' }));
50				var w = new Worker(url);
51				w.onmessage = function (e) {
52					w.terminate();
53					URL.revokeObjectURL(url);
54					resolve(e.data);
55				};
56				w.onerror = function (err) {
57					w.terminate();
58					URL.revokeObjectURL(url);
59					reject(err);
60				};
61				w.postMessage({ id: id, prefix: prefix });
62			} catch (e) {
63				reject(e);
64			}
65		});
66	}
67
68	function setHidden(form, name, value) {
69		var i = form.querySelector('input[name="' + name + '"]');
70		if (!i) {
71			i = document.createElement('input');
72			i.type = 'hidden';
73			i.name = name;
74			form.appendChild(i);
75		}
76		i.value = value;
77		// Also as an attribute: CF7 calls form.reset() after a send, which would
78		// otherwise blank a value that was only set on the property.
79		i.setAttribute('value', value);
80	}
81
82	function addHoneypot(form) {
83		if (form.querySelector('input[name="ommatic_website"]')) {
84			return;
85		}
86		var hp = document.createElement('input');
87		hp.type = 'text';
88		hp.name = 'ommatic_website';
89		hp.tabIndex = -1;
90		hp.autocomplete = 'off';
91		hp.setAttribute('aria-hidden', 'true');
92		hp.style.cssText =
93			'position:absolute!important;left:-9999px!important;top:auto;width:1px;height:1px;opacity:0;overflow:hidden;pointer-events:none';
94		form.appendChild(hp);
95	}
96
97	// The honeypot goes in right away, not with the token: bots fill the form on
98	// page load, and a honeypot that only appears once the token is ready was
99	// still empty when they did — their token-valid retry then passed.
100	function addHoneypots() {
101		var list = forms();
102		for (var i = 0; i < list.length; i++) {
103			addHoneypot(list[i]);
104		}
105	}
106
107	function injectAll() {
108		addHoneypots();
109		if (!token) {
110			return;
111		}
112		var list = forms();
113		for (var i = 0; i < list.length; i++) {
114			setHidden(list[i], 'ommatic_cap', token.cap);
115			setHidden(list[i], 'ommatic_pow', token.nonce);
116		}
117	}
118
119	// --- submit hold ------------------------------------------------------------
120	// A submit before the token is ready is held and replayed once it is, so a
121	// real visitor never posts without a token. The server can then treat a
122	// tokenless post as a bot signal and ban its IP + email.
123	var held = null; // { form, submitter } waiting for the token
124	var bypass = false; // set while replaying, so the hold lets it through
125
126	function resubmit(form, submitter) {
127		if (submitter && submitter.form !== form) {
128			submitter = null;
129		}
130		bypass = true;
131		try {
132			if (typeof form.requestSubmit === 'function') {
133				if (submitter) {
134					form.requestSubmit(submitter);
135				} else {
136					form.requestSubmit();
137				}
138			} else {
139				var btn = submitter || form.querySelector('[type="submit"]');
140				if (btn) {
141					btn.click();
142				}
143			}
144		} finally {
145			bypass = false;
146		}
147	}
148
149	function releaseHeld() {
150		if (!held) {
151			return;
152		}
153		var h = held;
154		held = null;
155		injectAll();
156		resubmit(h.form, h.submitter);
157	}
158
159	// Capture phase on the document runs before the form systems' own submit
160	// handlers (all bound on the form), so stopping here keeps them from posting.
161	function holdSubmit(e) {
162		var form = e.target;
163		if (token || bypass || !form || !form.matches || !form.matches(FORM_SELECTOR)) {
164			return;
165		}
166		e.preventDefault();
167		e.stopImmediatePropagation();
168		held = { form: form, submitter: e.submitter || null };
169		ensureToken();
170	}
171
172	function ensureToken() {
173		if (token || solving || !hasForm()) {
174			if (token) {
175				injectAll();
176			}
177			return;
178		}
179		solving = true;
180		fetch(cfg.challengeUrl, { headers: { Accept: 'application/json' }, credentials: 'same-origin' })
181			.then(function (r) {
182				return r.json();
183			})
184			.then(function (ch) {
185				return solvePow(ch.id, ch.zeros).then(function (nonce) {
186					token = { cap: ch.id + '.' + ch.iat + '.' + ch.zeros + '.' + ch.sig, nonce: nonce };
187					injectAll();
188				});
189			})
190			.catch(function () {
191				// leave forms without a token → server decides (fail-closed by default)
192			})
193			.finally(function () {
194				solving = false;
195				// Replay a held submit — with the token, or without it if solving
196				// failed, so the visitor at least gets the server's message instead
197				// of a dead button.
198				releaseHeld();
199			});
200	}
201
202	function boot() {
203		addHoneypots();
204		ensureToken();
205		document.addEventListener('submit', holdSubmit, true);
206
207		// CF7 keeps the visitor on the page after a send; the challenge is burned
208		// by then, so a second enquiry from the same page needs a fresh one.
209		document.addEventListener('wpcf7mailsent', function () {
210			token = null;
211			ensureToken();
212		});
213
214		// Elementor popups / AJAX render forms later — watch for them.
215		if ('MutationObserver' in window) {
216			var mo = new MutationObserver(function () {
217				if (hasForm()) {
218					addHoneypots();
219					if (token) {
220						injectAll();
221					} else {
222						ensureToken();
223					}
224				}
225			});
226			mo.observe(document.documentElement, { childList: true, subtree: true });
227		}
228	}
229
230	if (document.readyState === 'loading') {
231		document.addEventListener('DOMContentLoaded', boot, { once: true });
232	} else {
233		boot();
234	}
235})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.