src/components/recaptcha/ReCaptcha.js
/*globals grecaptcha*/
import Component from '../_classes/component/Component';
import { Formio } from '../../Formio';
import _get from 'lodash/get';
import _debounce from 'lodash/debounce';
export default class ReCaptchaComponent extends Component {
static schema(...extend) {
return Component.schema({
type: 'recaptcha',
key: 'recaptcha',
label: 'reCAPTCHA'
}, ...extend);
}
static get builderInfo() {
return {
title: 'reCAPTCHA',
group: 'premium',
icon: 'refresh',
documentation: '/userguide/form-building/premium-components#recaptcha',
weight: 40,
schema: ReCaptchaComponent.schema()
};
}
static savedValueTypes() {
return [];
}
render() {
this.recaptchaResult = null;
if (this.builderMode) {
return super.render('reCAPTCHA');
}
else {
return super.render('', true);
}
}
createInput() {
if (this.builderMode) {
// We need to see it in builder mode.
this.append(this.text(this.name));
}
else {
const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
if (siteKey) {
const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
}
else {
console.warn('There is no Site Key specified in settings in form JSON');
}
}
}
createLabel() {
return;
}
verify(actionName) {
const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
if (!siteKey) {
console.warn('There is no Site Key specified in settings in form JSON');
return;
}
if (!this.recaptchaApiReady) {
const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${_get(this.root.form, 'settings.recaptcha.siteKey')}`;
this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
}
if (this.recaptchaApiReady) {
this.recaptchaVerifiedPromise = new Promise((resolve, reject) => {
this.recaptchaApiReady
.then(() => {
if (!this.isLoading) {
this.isLoading= true;
grecaptcha.ready(_debounce(() => {
grecaptcha
.execute(siteKey, {
action: actionName
})
.then((token) => {
return this.sendVerificationRequest(token).then(({ verificationResult, token }) => {
this.recaptchaResult = {
...verificationResult,
token,
};
this.updateValue(this.recaptchaResult);
return resolve(verificationResult);
});
})
.catch(() => {
this.isLoading = false;
});
}, 1000));
}
})
.catch(() => {
return reject();
});
}).then(() => {
this.isLoading = false;
});
}
}
beforeSubmit() {
if (this.recaptchaVerifiedPromise) {
return this.recaptchaVerifiedPromise
.then(() => super.beforeSubmit());
}
return super.beforeSubmit();
}
sendVerificationRequest(token) {
return Formio.makeStaticRequest(`${Formio.projectUrl}/recaptcha?recaptchaToken=${token}`)
.then((verificationResult) => ({ verificationResult, token }));
}
checkComponentValidity(data, dirty, row, options = {}) {
data = data || this.rootValue;
row = row || this.data;
const { async = false } = options;
// Verification could be async only
if (!async) {
return super.checkComponentValidity(data, dirty, row, options);
}
const componentData = row[this.component.key];
if (!componentData || !componentData.token) {
this.setCustomValidity('ReCAPTCHA: Token is not specified in submission');
return Promise.resolve(false);
}
if (!componentData.success) {
this.setCustomValidity('ReCAPTCHA: Token validation error');
return Promise.resolve(false);
}
return this.hook('validateReCaptcha', componentData.token, () => Promise.resolve(true))
.then((success) => success)
.catch((err) => {
this.setCustomValidity(err.message || err);
return false;
});
}
normalizeValue(newValue) {
// If a recaptcha result has already been established, then do not allow it to be reset.
return this.recaptchaResult ? this.recaptchaResult : newValue;
}
}