Skip to content
Snippets Groups Projects

Issue #3248803: Add basic support for 'invisible' widget size

Files
5
+ 122
0
(function ($, Drupal, drupalSettings, once) {
'use strict';
function fetchHcaptchaToken(form, triggeringElemName) {
var promise = $.Deferred();
const invisibleWidget = document.querySelector(`#${form.id} .h-captcha[data-size="invisible"]`);
if (!invisibleWidget) {
return promise.resolve(true);
}
const triggerElem = form.querySelector(`[name="${triggeringElemName}"]`);
if (triggerElem) {
// Disable the triggering element before
// Drupal.Ajax.prototype.beforeSend() does, since the hCaptcha token
// round-trip is potentially slow. Logic below copies some ideas from
// ...beforeSend().
const triggerElemValue = ($.fieldValue && $.fieldValue(triggerElem)) || null;
triggerElem.setAttribute('disabled', true);
if (triggerElem.type !== 'submit' && triggerElemValue !== null) {
// This should only run for AJAX-submitted forms, triggering with
// non-submit elements.
const ajaxOpPreserve = form.querySelector('.hcaptcha-ajax-op-preserve') || document.createElement('input');
ajaxOpPreserve.setAttribute("type", "hidden");
ajaxOpPreserve.className = 'hcaptcha-ajax-op-preserve';
ajaxOpPreserve.name = triggerElem.name;
ajaxOpPreserve.value = triggerElemValue;
triggerElem.before(ajaxOpPreserve);
}
}
hcaptcha.execute(invisibleWidget.dataset.hcaptchaWidgetId, { async: true })
.then(function ({ response }) {
["h-captcha-response", 'g-recaptcha-response']
.map(elemName => form.querySelector(`[name="${elemName}"]`))
.filter(v => v) // keep only actual elements.
.forEach(elem => elem.value = response);
promise.resolve(true);
})
.catch(err => {
console.error(err);
// Simplistic handling of close/expired challenge; resolving the
// promise lets the caller continue the submission flow, which must
// then fail due to the missing hcaptcha response.
promise.resolve(false);
});
return promise;
}
// Handle ajax forms.
var originalAjaxSubmit;
Drupal.behaviors.hcaptchaAjaxSubmitOverride = {
attach: function (context, settings) {
if (originalAjaxSubmit || !$.fn.ajaxSubmit) return;
originalAjaxSubmit = $.fn.ajaxSubmit;
$.fn.ajaxSubmit = function (options, data, dataType, onSuccess) {
var self = this;
fetchHcaptchaToken(self[0], options.data._triggering_element_name)
.then(() => {
const origComplete = options.complete;
options.complete = function(xmlhttprequest, status) {
// Clean-up: remove hidden element used to mirror the disabled
// element.
self[0].querySelector('.hcaptcha-ajax-op-preserve')?.remove();
origComplete(xmlhttprequest, status);
};
originalAjaxSubmit.call(self, options, data, dataType, onSuccess);
});
};
}
};
Drupal.behaviors.hcaptchaInvisible = {
attach: function (context, settings) {
// Handle non-ajax forms.
let isFetchingToken = false;
const formsWithInvisible = Array.from(document.querySelectorAll('.h-captcha[data-size="invisible"]'))
.map((elem) => elem.closest('form'));
$(formsWithInvisible).one('submit', function (e) {
const self = this;
if (e.originalEvent.submitter.getAttribute('formnovalidate')) {
return;
}
if (isFetchingToken) return false;
isFetchingToken = true;
fetchHcaptchaToken(self, e.originalEvent.submitter && e.originalEvent.submitter.getAttribute('name'))
.then(() => {
isFetchingToken = false;
self.submit();
});
return false;
});
// Calling formElement.submit() (see above) causes the POST data to not
// include [type=submit] elements. Let's inject a hidden element to mimic
// name + value for the type=submit element that was clicked to trigger
// the form submission.
once('hcaptcha-op-preserve', formsWithInvisible).forEach((theForm) => {
const opPreserve = document.createElement("input");
opPreserve.setAttribute("type", "hidden");
$(theForm).on('click', '[type=submit]', function(e) {
const submitId = e.currentTarget.id;
// Do not do this on AJAX-y submissions.
if (!drupalSettings.ajax || !drupalSettings.ajax.hasOwnProperty(submitId)) {
opPreserve.name = this.name;
opPreserve.value = this.value;
theForm.appendChild(opPreserve);
}
});
});
}
};
})(jQuery, Drupal, drupalSettings, once);
Loading