Skip to content

Commit

Permalink
fix(recaptcha): add safeguards for race conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
chickenn00dle committed Dec 12, 2024
1 parent 5b2954d commit e0ffd43
Showing 1 changed file with 32 additions and 17 deletions.
49 changes: 32 additions & 17 deletions src/other-scripts/recaptcha/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ const isV3 = 'v3' === newspack_recaptcha_data.version;
const siteKey = newspack_recaptcha_data.site_key;
const isInvisible = 'v2_invisible' === newspack_recaptcha_data.version;

/**
* Destroy hidden reCAPTCHA v3 token fields to avoid unnecessary reCAPTCHA checks.
*/
function destroy( forms = [] ) {
if ( isV3 ) {
const formsToHandle = forms.length
? forms
: [ ...document.querySelectorAll( 'form[data-newspack-recaptcha]' ) ];

formsToHandle.forEach( form => {
removeHiddenField( form );
} );
}
}

/**
* Refresh the reCAPTCHA v3 token for the given form and action.
*
Expand Down Expand Up @@ -140,6 +155,12 @@ function renderWidget( form, onSuccess = null, onError = null ) {
return;
}

// Don't render widget if the button is currently rendering recaptcha.
if ( button.hasAttribute( 'data-recaptcha-processing' ) ) {
return;
}
button.setAttribute( 'data-recaptcha-processing', 'true' );

// Callback when reCAPTCHA passes validation.
const successCallback = () => {
onSuccess?.()
Expand All @@ -160,8 +181,8 @@ function renderWidget( form, onSuccess = null, onError = null ) {
clearInterval( refreshIntervalId );
}
const message = retryCount < 3
? wp.i18n.__( 'There was an error with reCAPTCHA. Please try again.', 'newspack-plugin' )
: wp.i18n.__( 'There was an error with reCAPTCHA. Please reload the page and try again.', 'newspack-plugin' );
? wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please try submitting again.', 'newspack-plugin' )
: wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please reload the page and try again.', 'newspack-plugin' );
if ( onError ) {
onError( message );
} else {
Expand All @@ -170,20 +191,25 @@ function renderWidget( form, onSuccess = null, onError = null ) {
alert( message );
}
},
'expired-callback': () => {
refreshWidget( button );
},
} );

button.setAttribute( 'data-recaptcha-widget-id', widgetId );
const refreshIntervalId = setInterval( () => refreshWidget( button ), 120000 ); // Refresh widget every 2 minutes.

button.addEventListener( 'click', e => {
e.preventDefault();
e.stopImmediatePropagation();
// Skip reCAPTCHA verification if the button has a data-skip-recaptcha attribute.
if ( button.hasAttribute( 'data-skip-recaptcha' ) ) {
successCallback();
} else {
grecaptcha.execute( widgetId );
}
} );
button.removeAttribute( 'data-recaptcha-processing' );
} );
}

Expand All @@ -205,30 +231,19 @@ function render( forms = [], onSuccess = null, onError = null ) {
: [ ...document.querySelectorAll( 'form[data-newspack-recaptcha]' ) ];

formsToHandle.forEach( form => {
if ( form.hasAttribute( 'data-recaptcha-rendered' ) ) {
return;
}
if ( isV3 ) {
addHiddenField( form );
}
if ( isV2 ) {
renderWidget( form, onSuccess, onError );
}
form.setAttribute( 'data-recaptcha-rendered', 'true' );
} );
}

/**
* Destroy hidden reCAPTCHA v3 token fields to avoid unnecessary reCAPTCHA checks.
*/
function destroy( forms = [] ) {
if ( isV3 ) {
const formsToHandle = forms.length
? forms
: [ ...document.querySelectorAll( 'form[data-newspack-recaptcha]' ) ];

formsToHandle.forEach( form => {
removeHiddenField( form );
} );
}
}

/**
* Invoke only after reCAPTCHA API is ready.
*/
Expand Down

0 comments on commit e0ffd43

Please sign in to comment.