File size: 1,377 Bytes
ec8acdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const TURNSTILE_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
const MAX_LOAD_ATTEMPTS = 3;

let loadAttempts = 0;
let pending: Promise<boolean> | null = null;

/**
 * Lazily inject the Turnstile challenge script. Resolves true once
 * window.turnstile is available, false when the load failed. A failed or
 * blocked request does NOT poison the page session: the attempt counter
 * (not an injected flag) gates retries, so the next trigger re-injects,
 * bounded at MAX_LOAD_ATTEMPTS. Injected scripts inherit trust under the
 * CSP's 'strict-dynamic'; the nonce covers browsers that predate it.
 */
export function ensureTurnstileScript(): Promise<boolean> {
  if (window.turnstile) return Promise.resolve(true);
  if (pending) return pending;
  if (loadAttempts >= MAX_LOAD_ATTEMPTS) return Promise.resolve(false);
  loadAttempts++;
  pending = new Promise<boolean>((settle) => {
    const script = document.createElement('script');
    script.src = TURNSTILE_SRC;
    script.async = true;
    script.nonce = 'wm-static-bootstrap';
    script.addEventListener('load', () => {
      pending = null;
      settle(true);
    }, { once: true });
    script.addEventListener('error', () => {
      script.remove();
      pending = null;
      settle(false);
    }, { once: true });
    document.head.appendChild(script);
  });
  return pending;
}