PageSourceSearch

https://ilindy.com/turnstile.js

js ilindy.com collected 2026-09-26 09:55:26 UTC 4,634 bytes, 115 lines download raw bytes

1/**
2 * Cloudflare Turnstile, loaded only when somebody actually touches a form.
3 *
4 * The honeypot already catches the bot that fills every field it finds, which
5 * is most automated traffic. What it cannot catch is somebody who looked at
6 * the form once, saw the trap and left it empty — and the newsletter form
7 * writes to GoHighLevel, where the cost of that is not noise but a
8 * contaminated list with welcome sequences firing at invented addresses.
9 *
10 * Three decisions worth stating, because each has a cost somebody will
11 * otherwise pay without knowing.
12 *
13 * Turnstile rather than reCAPTCHA: reCAPTCHA sends the visitor's data to
14 * Google, from a site that carries a consent banner, which turns a spam
15 * decision into a privacy one. Turnstile sends nothing to Google and is free
16 * without a quota.
17 *
18 * Loaded on first focus rather than on page load. The script is ~35 KB
19 * against a home page that is 190 KB — an eighteen percent tax on everybody,
20 * to protect a form that most visitors never touch. Deferred to the first
21 * interaction, the people who submit pay it and nobody else does.
22 *
23 * Inert without a site key. Absent one the forms behave exactly as they did
24 * before, so this can ship before the Cloudflare account exists rather than
25 * breaking three forms until somebody sets an environment variable.
26 */
27(function () {
28  var KEY = document.documentElement.dataset.turnstileKey || '';
29  if (!KEY) return;                       // not configured: do nothing at all
30
31  var SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
32  var loading = null;
33  var widgets = new WeakMap();            // form -> { id, token, waiters }
34
35  function load() {
36    if (loading) return loading;
37    loading = new Promise(function (resolve, reject) {
38      var s = document.createElement('script');
39      s.src = SRC;
40      s.async = true;
41      s.defer = true;
42      s.onload = function () { resolve(window.turnstile); };
43      s.onerror = function () { reject(new Error('turnstile unavailable')); };
44      document.head.appendChild(s);
45    });
46    return loading;
47  }
48
49  /**
50   * One widget per form, rendered the moment a field is touched so the
51   * challenge — on the rare visitor who gets one — is solved by the time they
52   * finish typing rather than after they press the button.
53   */
54  function arm(form) {
55    if (widgets.has(form)) return widgets.get(form);
56
57    var state = { id: null, token: '', waiters: [] };
58    widgets.set(form, state);
59
60    var host = document.createElement('div');
61    host.className = 'cf-turnstile-host';
62    form.appendChild(host);
63
64    load().then(function (turnstile) {
65      state.id = turnstile.render(host, {
66        sitekey: KEY,
67        // Shows itself only when Cloudflare wants a human to do something.
68        // Everybody else sees nothing at all.
69        appearance: 'interaction-only',
70        theme: 'dark',
71        callback: function (token) {
72          state.token = token;
73          state.waiters.splice(0).forEach(function (r) { r(token); });
74        },
75        'expired-callback': function () { state.token = ''; },
76        'error-callback': function () {
77          // Cloudflare could not decide. Release anything waiting with an
78          // empty token and let the server make the call — the alternative is
79          // a form that never submits because a third party had a bad minute.
80          state.token = '';
81          state.waiters.splice(0).forEach(function (r) { r(''); });
82        },
83      });
84    }).catch(function () {
85      state.waiters.splice(0).forEach(function (r) { r(''); });
86    });
87
88    return state;
89  }
90
91  /**
92   * The token for this form, or '' if there will not be one.
93   *
94   * Bounded, because a promise that never settles is a submit button that
95   * never comes back. Four seconds is long enough for a challenge to solve
96   * itself and short enough that nobody wonders whether the page is broken.
97   */
98  window.iLindyTurnstile = function (form) {
99    var state = arm(form);
100    if (state.token) return Promise.resolve(state.token);
101    return new Promise(function (resolve) {
102      state.waiters.push(resolve);
103      setTimeout(function () {
104        var i = state.waiters.indexOf(resolve);
105        if (i !== -1) { state.waiters.splice(i, 1); resolve(state.token || ''); }
106      }, 4000);
107    });
108  };
109
110  // Arm on the first touch of any field inside a form we protect.
111  document.addEventListener('focusin', function (e) {
112    var form = e.target && e.target.closest && e.target.closest('form');
113    if (form && form.dataset.protected !== undefined) arm(form);
114  }, { passive: true });
115})();

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.