(function() { 'use strict'; var CONFIG = { provider: "grecaptcha", scriptUrl: "https:\/\/www.google.com\/recaptcha\/api.js", cssClass: "g-recaptcha", sitekey: "6Le3yDwUAAAAALgkwPt8DzLin8nksZZk3CUeGkLg", responseField: "g-recaptcha-response" }; var providerReadyPromise = loadProviderScript(CONFIG.scriptUrl); // Emite os eventos de ciclo de vida do captcha na própria div alvo. function emit(target, type, detail) { target.dispatchEvent(new CustomEvent('captcha:' + type, { detail: detail || {}, bubbles: true })); } // Conjunto de callbacks que o provider invoca e que reemitimos como // eventos do DOM na div alvo. function buildCallbacks(target) { return { callback: function(token) { emit(target, 'solved', { token: token, responseField: CONFIG.responseField }); }, 'expired-callback': function() { emit(target, 'expired', {}); }, 'error-callback': function() { emit(target, 'error', {}); } }; } function render(elementOrId) { var target = typeof elementOrId === 'string' ? document.getElementById(elementOrId) : elementOrId; if (!target) { throw new Error('Elemento para o captcha não encontrado'); } // Renderização sempre explícita, tanto para SPA (elemento inserido // depois) quanto para páginas server-side (elemento já presente). // Não dependemos do auto-render implícito do provider: como o script // dele é injetado dinamicamente por este loader, esse modo não // dispara de forma confiável. Por isso também NÃO marcamos o elemento // com a classe de auto-render — evita render duplicado. return providerReadyPromise.then(function() { renderWidget(target); target.dispatchEvent(new CustomEvent('captcha:ready', { detail: { provider: CONFIG.provider, responseField: CONFIG.responseField }, bubbles: true })); return target; }); } function renderWidget(target) { // Evita renderizar o mesmo elemento mais de uma vez if (target.dataset.captchaRendered === 'true') { return; } var api = window[CONFIG.provider]; if (!api) { throw new Error('API do provider "' + CONFIG.provider + '" indisponível'); } function doRender() { if (typeof api.render !== 'function') { throw new Error('API do provider "' + CONFIG.provider + '" não expõe render()'); } var opts = buildCallbacks(target); opts.sitekey = CONFIG.sitekey; target.captchaWidgetId = api.render(target, opts); target.dataset.captchaRendered = 'true'; } if (typeof api.ready === 'function') { try { api.ready(doRender); } catch (e) { doRender(); } } else { doRender(); } } window.captcha = { render: render, getToken: function(scope) { scope = scope || document; var input = scope.querySelector( '[name="' + CONFIG.responseField + '"]' ); return input && input.value ? input.value : null; }, reset: function(elementOrId) { var el = typeof elementOrId === 'string' ? document.getElementById(elementOrId) : elementOrId; var api = window[CONFIG.provider]; if (el && el.captchaWidgetId !== undefined) { api.reset(el.captchaWidgetId); } else { api.reset(); } }, get fieldName() { return CONFIG.responseField; } }; // Compatibilidade com páginas tradicionais. document.currentScript é null // quando o loader é injetado dinamicamente ou carregado como módulo; nesse // caso a app deve chamar window.captcha.render() por conta própria. var SCRIPT_TAG = document.currentScript; var TARGET_ID = SCRIPT_TAG && SCRIPT_TAG.dataset.target; if (TARGET_ID) { render(TARGET_ID).catch(function(err) { console.error('[captcha]', err); }); } function loadProviderScript(url) { return new Promise(function(resolve, reject) { if (window[CONFIG.provider]) { resolve(); return; } var existing = document.querySelector( 'script[src="' + url + '"]' ); if (existing) { if (existing.dataset.loaded === 'true') { resolve(); return; } existing.addEventListener('load', resolve); existing.addEventListener('error', reject); return; } var s = document.createElement('script'); s.src = url; s.async = true; s.defer = true; s.onload = function() { s.dataset.loaded = 'true'; resolve(); }; s.onerror = function() { reject(new Error('script load failed')); }; document.head.appendChild(s); }); } })();