PageSourceSearch

https://site.nl/_static/57b54aa969c5e910/scripts/features/common/auth/captcha-adapter.js

js site.nl collected 2026-10-02 15:06:24 UTC 5,403 bytes, 217 lines download raw bytes

1const HCAPTCHA_SITE_KEY = '55260743-d3dc-480a-818b-309e0eca304f';
2
3const createCaptchaAdapter = ({
4  form,
5  rememberMeLabel,
6  siteKey = HCAPTCHA_SITE_KEY,
7  onSolved = async () => {},
8  onStateChange = () => {},
9  onError = () => {},
10} = {}) => {
11  if (!(form instanceof HTMLFormElement)) {
12    return null;
13  }
14
15  let isActive = false;
16  let isRetrying = false;
17  let captchaContainer = form.querySelector('[data-auth-captcha-container]');
18  let captchaWidgetId = null;
19  let captchaSdkPromise = null;
20
21  const emitStateChange = () => {
22    if (typeof onStateChange === 'function') {
23      onStateChange(isActive);
24    }
25  };
26
27  const ensureCaptchaContainer = () => {
28    if (captchaContainer instanceof HTMLElement && captchaContainer.isConnected) {
29      return captchaContainer;
30    }
31
32    const existing = form.querySelector('[data-auth-captcha-container]');
33    if (existing instanceof HTMLElement) {
34      captchaContainer = existing;
35      return captchaContainer;
36    }
37
38    if (!(rememberMeLabel instanceof HTMLElement)) {
39      return null;
40    }
41
42    const container = document.createElement('div');
43    container.setAttribute('data-auth-captcha-container', '');
44    container.hidden = true;
45    rememberMeLabel.insertAdjacentElement('afterend', container);
46    captchaContainer = container;
47    return captchaContainer;
48  };
49
50  const setCaptchaVisible = (visible) => {
51    const container = ensureCaptchaContainer();
52    if (!(container instanceof HTMLElement)) {
53      return;
54    }
55    container.hidden = !visible;
56  };
57
58  const removeCaptchaWidget = () => {
59    const api = window.hcaptcha;
60    if (api && captchaWidgetId !== null && typeof api.remove === 'function') {
61      try {
62        api.remove(captchaWidgetId);
63      } catch {}
64    }
65    captchaWidgetId = null;
66  };
67
68  const loadCaptchaSdk = async () => {
69    if (window.hcaptcha && typeof window.hcaptcha.render === 'function') {
70      return window.hcaptcha;
71    }
72
73    if (captchaSdkPromise) {
74      return captchaSdkPromise;
75    }
76
77    captchaSdkPromise = new Promise((resolve, reject) => {
78      const existingScript = document.querySelector('script[src*="js.hcaptcha.com/1/api.js"]');
79      if (!(existingScript instanceof HTMLScriptElement)) {
80        reject(new Error('Missing hCaptcha SDK script'));
81        return;
82      }
83
84      let timeout;
85      const cleanup = () => {
86        window.clearTimeout(timeout);
87        existingScript.removeEventListener('load', onLoad);
88        existingScript.removeEventListener('error', onLoadError);
89      };
90      const resolveApi = () => {
91        cleanup();
92        const api = window.hcaptcha;
93        if (api && typeof api.render === 'function') {
94          resolve(api);
95          return;
96        }
97        reject(new Error('hCaptcha SDK unavailable'));
98      };
99
100      const onLoad = () => resolveApi();
101      const onLoadError = () => {
102        cleanup();
103        reject(new Error('Failed to load hCaptcha SDK'));
104      };
105
106      existingScript.addEventListener('load', onLoad, { once: true });
107      existingScript.addEventListener('error', onLoadError, { once: true });
108      timeout = window.setTimeout(resolveApi, 10000);
109    }).catch((error) => {
110      captchaSdkPromise = null;
111      throw error;
112    });
113
114    return captchaSdkPromise;
115  };
116
117  const stop = () => {
118    isActive = false;
119    setCaptchaVisible(false);
120    removeCaptchaWidget();
121    emitStateChange();
122  };
123
124  const handleSolved = async (token) => {
125    if (!isActive) {
126      return;
127    }
128
129    stop();
130    await onSolved(token);
131  };
132
133  const recreate = async () => {
134    if (!isActive || isRetrying) {
135      return false;
136    }
137
138    isRetrying = true;
139    try {
140      setCaptchaVisible(true);
141
142      const api = await loadCaptchaSdk();
143      if (!api || typeof api.render !== 'function') {
144        throw new Error('Missing hCaptcha API');
145      }
146
147      const container = ensureCaptchaContainer();
148      if (!(container instanceof HTMLElement)) {
149        throw new Error('Missing captcha container');
150      }
151
152      removeCaptchaWidget();
153      container.replaceChildren();
154
155      const widgetNode = document.createElement('div');
156      widgetNode.className = 'h-captcha';
157      widgetNode.setAttribute('data-sitekey', siteKey);
158      container.appendChild(widgetNode);
159
160      const renderedWidgetId = api.render(widgetNode, {
161        sitekey: siteKey,
162        callback: (token) => {
163          void handleSolved(token);
164        },
165        'expired-callback': () => {
166          void recreate();
167        },
168        'error-callback': () => {
169          stop();
170          onError();
171        },
172      });
173
174      if (renderedWidgetId === null || renderedWidgetId === undefined) {
175        throw new Error('Failed to render captcha widget');
176      }
177
178      captchaWidgetId = renderedWidgetId;
179      return true;
180    } catch {
181      stop();
182      onError();
183      return false;
184    } finally {
185      isRetrying = false;
186    }
187  };
188
189  const start = async () => {
190    if (isActive && captchaWidgetId !== null) {
191      return true;
192    }
193
194    isActive = true;
195    emitStateChange();
196
197    const started = await recreate();
198    if (!started) {
199      if (isActive) {
200        stop();
201      }
202      return false;
203    }
204
205    return true;
206  };
207
208  return {
209    isActive: () => isActive,
210    start,
211    stop,
212    setVisible: setCaptchaVisible,
213    recreate,
214  };
215};
216
217export { HCAPTCHA_SITE_KEY, createCaptchaAdapter };

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.