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.