PluginProbe
Passster – Password Protect Pages and Content / 4.3.17
Passster – Password Protect Pages and Content v4.3.17
4.3.17 4.3.16 4.3.15 4.3.14 4.3.12 4.3.13 4.3.11 4.3.10 4.3.9 4.3.8 4.3.7 4.3.6 4.3.5 trunk 3.5.4 3.5.5.2 3.5.5.8 3.5.5.9 4.0 4.1.4 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 All 49 releases
← All changes | assets/public/passster-public.js +343 -388 4.2.13 → 4.3.17 View file →
@@ -1,388 +1,343 @@
1 -jQuery(document).ready(function ($) {
2 -
3 - // Get cookie duration.
4 - function getDurationBySettings() {
5 - switch (ps_ajax.cookie_duration_unit) {
6 - case 'days':
7 - return parseInt(ps_ajax.cookie_duration);
8 - break;
9 - case 'hours':
10 - return new Date(new Date().getTime() + (ps_ajax.cookie_duration * 10) * 60 * 1000);
11 - break;
12 - case 'minutes':
13 - return new Date(new Date().getTime() + ps_ajax.cookie_duration * 60 * 1000);
14 - break;
15 - default:
16 - return parseInt(ps_ajax.cookie_duration);
17 - }
18 - }
19 -
20 - // Get cache busting URL.
21 - function getCacheFriendlyURL() {
22 - if (location.search) {
23 - return (location.origin).concat(location.pathname).concat(location.hash) + location.search + '&pts=' + Math.floor(Date.now() / 1000);
24 - } else {
25 - return (location.origin).concat(location.pathname).concat(location.hash) + '?pts=' + Math.floor(Date.now() / 1000);
26 - }
27 - }
28 -
29 - // Get hashed cookie via Ajax.
30 - function setHashedCookie(password) {
31 - $.ajax({
32 - type: "post", dataType: "json", url: ps_ajax.ajax_url, data: {
33 - 'action': 'hash_password', 'hash_nonce': ps_ajax.hash_nonce, 'password': password,
34 - }, success: function (response) {
35 - Cookies.set('passster', response.password, {
36 - expires: getDurationBySettings(), sameSite: 'strict'
37 - });
38 - }, async: false,
39 - });
40 - }
41 -
42 - // Check if we have an unlock link.
43 - if (ps_ajax.link_pass) {
44 - if (!ps_ajax.disable_cookie) {
45 - if (ps_ajax.link_pass.length < 25) {
46 - // It's an old base64 encryption.
47 - setHashedCookie(atob(ps_ajax.link_pass));
48 - } else {
49 - Cookies.set('passster', ps_ajax.link_pass, {
50 - expires: getDurationBySettings(), sameSite: 'strict'
51 - });
52 - }
53 -
54 - // Handle the redirect with cache busting.
55 - window.location.replace(ps_ajax.permalink + '?pts=' + Math.floor(Date.now() / 1000));
56 - }
57 - }
58 -
59 - // Passwords
60 - $('.passster-submit').on('click', function (e) {
61 - e.preventDefault();
62 -
63 - // Validate form before submitting ajax.
64 - var form = $(this).parent().parent();
65 -
66 - if (!$(form)[0].checkValidity()) {
67 - $(form)[0].reportValidity();
68 - }
69 -
70 - ps_id = $(this).attr('data-psid');
71 - form = $("#" + ps_id);
72 - password = $("#" + ps_id + ' .passster-password').attr('data-password');
73 - type = $("#" + ps_id + ' .passster-password').attr('data-protection-type');
74 - list = $("#" + ps_id + ' .passster-password').attr('data-list');
75 - lists = $("#" + ps_id + ' .passster-password').attr('data-lists');
76 - area = $("#" + ps_id + ' .passster-password').attr('data-area');
77 - protection = $("#" + ps_id + ' .passster-password').attr('data-protection');
78 - redirect = $(this).attr('data-redirect');
79 - input = $("#" + ps_id + ' .passster-password').val();
80 - acf = $(this).attr('data-acf');
81 -
82 - $.ajax({
83 - type: "post", dataType: "json", url: ps_ajax.ajax_url, data: {
84 - 'action': 'validate_input',
85 - 'nonce': ps_ajax.nonce,
86 - 'input': input,
87 - 'password': password,
88 - 'post_id': ps_ajax.post_id,
89 - 'type': type,
90 - 'list': list,
91 - 'lists': lists,
92 - 'area': area,
93 - 'protection': protection,
94 - 'acf': acf,
95 - 'redirect': redirect
96 - }, beforeSend: function () {
97 - form.find(".ps-loader").css('display', 'block');
98 - }, success: function (response) {
99 - form.find(".ps-loader").css('display', 'none');
100 - if (true === response.success) {
101 - // if no ajax.
102 - if (!ps_ajax.unlock_mode) {
103 - setHashedCookie(input);
104 -
105 - if (response.redirect) {
106 - window.location.replace(redirect);
107 - } else {
108 - window.location.href = getCacheFriendlyURL();
109 - }
110 - } else {
111 -
112 - // set cookie if activated.
113 - if (!ps_ajax.disable_cookie) {
114 - setHashedCookie(input);
115 - }
116 - form.find('.passster-error').hide();
117 -
118 - // replace shortcodes.
119 - let content = response.content;
120 -
121 - if (content) {
122 - $.each(ps_ajax.shortcodes, function (key, value) {
123 - content = content.replace(key, value);
124 - });
125 -
126 - $("#" + ps_id).replaceWith(content);
127 - }
128 -
129 - // Redirect?
130 - if (response.redirect) {
131 - window.location.replace(redirect);
132 - }
133 -
134 - }
135 - } else {
136 - form.find('.passster-error').text(response.error);
137 - form.find('.passster-error').show().fadeOut(3500);
138 - $("#" + ps_id + ' .passster-password').val('');
139 - }
140 - }
141 - });
142 - });
143 -
144 - // Recaptcha v2
145 - if ($('.recaptcha-form-v2').length > 0) {
146 - grecaptcha.ready(function () {
147 - grecaptcha.render('ps-recaptcha-v2', {
148 - 'sitekey': ps_ajax.recaptcha_key, 'callback': function (token) {
149 - ps_id = $('.recaptcha-v2-submit').attr('data-psid');
150 - form = $("#" + ps_id);
151 - protection = $('.recaptcha-v2-submit').attr('data-protection');
152 - acf = $('.recaptcha-v2-submit').attr('data-acf');
153 - area = $("#" + ps_id).find('.recaptcha-v2-submit').attr('data-area');
154 - redirect = $("#" + ps_id).find('.recaptcha-v2-submit').attr('data-redirect');
155 -
156 - $.ajax({
157 - type: "post", dataType: "json", url: ps_ajax.ajax_url, data: {
158 - 'action': 'validate_input',
159 - 'nonce': ps_ajax.nonce,
160 - 'token': token,
161 - 'post_id': ps_ajax.post_id,
162 - 'type': 'recaptcha',
163 - 'protection': protection,
164 - 'captcha_id': ps_id,
165 - 'acf': acf,
166 - 'area': area,
167 - 'redirect': redirect
168 - }, success: function (response) {
169 - if (true === response.success) {
170 -
171 - // if no ajax.
172 - if (!ps_ajax.unlock_mode) {
173 - Cookies.set('passster', 'recaptcha', {
174 - expires: getDurationBySettings(), sameSite: 'strict'
175 - });
176 -
177 - if (response.redirect) {
178 - window.location.replace(redirect);
179 - } else {
180 - window.location.href = getCacheFriendlyURL();
181 - }
182 - } else {
183 - // set cookie if activated.
184 - if (!ps_ajax.disable_cookie) {
185 - Cookies.set('passster', 'recaptcha', {
186 - expires: getDurationBySettings(), sameSite: 'strict'
187 - });
188 - }
189 - form.find('.passster-error').hide();
190 -
191 - // replace shortcodes.
192 - let content = response.content;
193 -
194 - if (content) {
195 -
196 - $.each(ps_ajax.shortcodes, function (key, value) {
197 - content = content.replace(key, value);
198 - });
199 -
200 - $("#" + ps_id).replaceWith(content);
201 - }
202 -
203 - // Redirect?
204 - if (response.redirect) {
205 - window.location.replace(redirect);
206 - }
207 - }
208 - } else {
209 - form.find('.passster-error').text(response.error);
210 - form.find('.passster-error').show().fadeOut(3500);
211 - }
212 - }
213 - });
214 - }
215 - });
216 - });
217 - }
218 -
219 - // ReCaptcha v3
220 - $('.recaptcha-form').on('submit', function (event) {
221 - event.preventDefault();
222 -
223 - ps_id = $(this).find('.passster-submit-recaptcha').attr('data-psid');
224 - form = $("#" + ps_id);
225 - protection = $(this).find('.passster-submit-recaptcha').attr('data-protection');
226 - acf = $(this).find('.passster-submit-recaptcha').attr('data-acf');
227 - area = $(this).find('.passster-submit-recaptcha').attr('data-area');
228 - redirect = $(this).find('.passster-submit-recaptcha').attr('data-redirect');
229 -
230 - grecaptcha.ready(function () {
231 - grecaptcha.execute(ps_ajax.recaptcha_key, {action: 'validate_input'}).then(function (token) {
232 -
233 - form.prepend('<input type="hidden" name="token" value="' + token + '">');
234 - form.prepend('<input type="hidden" name="action" value="validate_input">');
235 -
236 - $.ajax({
237 - type: "post", dataType: "json", url: ps_ajax.ajax_url, data: {
238 - 'action': 'validate_input',
239 - 'nonce': ps_ajax.nonce,
240 - 'token': token,
241 - 'post_id': ps_ajax.post_id,
242 - 'type': 'recaptcha',
243 - 'protection': protection,
244 - 'captcha_id': ps_id,
245 - 'acf': acf,
246 - 'area': area,
247 - 'redirect': redirect
248 - }, success: function (response) {
249 - if (true === response.success) {
250 -
251 - // if no ajax.
252 - if (!ps_ajax.unlock_mode) {
253 - Cookies.set('passster', 'recaptcha', {
254 - expires: getDurationBySettings(), sameSite: 'strict'
255 - });
256 -
257 - if (response.redirect) {
258 - window.location.replace(redirect);
259 - } else {
260 - window.location.href = getCacheFriendlyURL();
261 - }
262 - } else {
263 - // set cookie if activated.
264 - if (!ps_ajax.disable_cookie) {
265 - Cookies.set('passster', 'recaptcha', {
266 - expires: getDurationBySettings(), sameSite: 'strict'
267 - });
268 - }
269 - form.find('.passster-error').hide();
270 - // replace shortcodes.
271 - let content = response.content;
272 -
273 - if (content) {
274 - $.each(ps_ajax.shortcodes, function (key, value) {
275 - content = content.replace(key, value);
276 - });
277 -
278 - form.replaceWith(content);
279 - }
280 -
281 - // Redirect?
282 - if (response.redirect) {
283 - window.location.replace(redirect);
284 - }
285 - }
286 - } else {
287 - form.find('.passster-error').text(response.error);
288 - form.find('.passster-error').show().fadeOut(3500);
289 - }
290 - }
291 - });
292 - });
293 - });
294 - });
295 -
296 - // hcaptcha
297 - $('.hcaptcha-form').on('submit', function (event) {
298 - event.preventDefault();
299 -
300 - ps_id = $(this).find('.passster-submit-recaptcha').attr('data-psid');
301 - form = $("#" + ps_id);
302 - protection = $(this).find('.passster-submit-recaptcha').attr('data-protection');
303 - acf = $(this).find('.passster-submit-recaptcha').attr('data-acf');
304 - area = $(this).find('.passster-submit-recaptcha').attr('data-area');
305 - redirect = $(this).find('.passster-submit-recaptcha').attr('data-redirect');
306 -
307 - hcaptcha.execute({async: true})
308 - .then(({response, key}) => {
309 - $.ajax({
310 - type: "post", dataType: "json", url: ps_ajax.ajax_url, data: {
311 - 'action': 'validate_input',
312 - 'nonce': ps_ajax.nonce,
313 - 'token': response,
314 - 'post_id': ps_ajax.post_id,
315 - 'type': 'recaptcha',
316 - 'protection': protection,
317 - 'captcha_id': ps_id,
318 - 'acf': acf,
319 - 'area': area,
320 - 'redirect': redirect
321 - }, success: function (response) {
322 - if (true === response.success) {
323 -
324 - // if no ajax.
325 - if (!ps_ajax.unlock_mode) {
326 - Cookies.set('passster', 'recaptcha', {
327 - expires: getDurationBySettings(), sameSite: 'strict'
328 - });
329 -
330 - if (response.redirect) {
331 - window.location.replace(redirect);
332 - } else {
333 - window.location.href = getCacheFriendlyURL();
334 - }
335 - } else {
336 - // set cookie if activated.
337 - if (!ps_ajax.disable_cookie) {
338 - Cookies.set('passster', 'recaptcha', {
339 - expires: getDurationBySettings(), sameSite: 'strict'
340 - });
341 - }
342 - form.find('.passster-error').hide();
343 - // replace shortcodes.
344 - let content = response.content;
345 -
346 - if (content) {
347 - $.each(ps_ajax.shortcodes, function (key, value) {
348 - content = content.replace(key, value);
349 - });
350 -
351 - form.replaceWith(content);
352 - }
353 -
354 - // Redirect?
355 - if (response.redirect) {
356 - window.location.replace(redirect);
357 - }
358 - }
359 - } else {
360 - form.find('.passster-error').text(response.error);
361 - form.find('.passster-error').show().fadeOut(3500);
362 - }
363 - }
364 - });
365 - })
366 - .catch(err => {
367 - form.find('.passster-error').text(err);
368 - form.find('.passster-error').show().fadeOut(3500);
369 - });
370 -
371 - });
372 -
373 - // Concurrent logout.
374 - $(document).on('click', '#ps-logout', function () {
375 - $.ajax({
376 - type: 'post',
377 - dataType: 'json',
378 - url: ps_ajax.ajax_url,
379 - data: {'action': 'handle_logout', 'logout_nonce': ps_ajax.logout_nonce},
380 - success: function (response) {
381 - if (true === response.success) {
382 - Cookies.set('passster', '', {expires: 0, sameSite: 'strict'});
383 - window.location.href = getCacheFriendlyURL();
384 - }
385 - }
386 - });
387 - });
388 -});
1 +jQuery( document ).ready( function( $ ) {
2 + const restUrl = ps_ajax.rest_url + 'passster/v1';
3 +
4 + // Get cookie duration.
5 + function getDurationBySettings() {
6 + switch ( ps_ajax.cookie_duration_unit ) {
7 + case 'days':
8 + return parseInt( ps_ajax.cookie_duration );
9 + case 'hours':
10 + return new Date( new Date().getTime() + ( ps_ajax.cookie_duration * 60 ) * 60 * 1000 );
11 + case 'minutes':
12 + return new Date( new Date().getTime() + ps_ajax.cookie_duration * 60 * 1000 );
13 + default:
14 + return parseInt( ps_ajax.cookie_duration );
15 + }
16 + }
17 +
18 + // Get cache busting URL.
19 + function getCacheFriendlyURL() {
20 + const pts = 'pts=' + Math.floor( Date.now() / 1000 );
21 + const base = location.origin + location.pathname;
22 + const search = location.search ? location.search + '&' + pts : '?' + pts;
23 + const hash = location.hash || '';
24 + return base + search + hash;
25 + }
26 +
27 + // Hash password via REST API.
28 + async function hashPassword( password, postId ) {
29 + try {
30 + const response = await fetch( restUrl + '/hash', {
31 + method: 'POST',
32 + credentials: 'same-origin',
33 + headers: { 'Content-Type': 'application/json' },
34 + body: JSON.stringify( { password, post_id: postId } ),
35 + } );
36 + const data = await response.json();
37 + return data.hash || '';
38 + } catch ( e ) {
39 + return '';
40 + }
41 + }
42 +
43 + // Get existing hashes from cookie as array.
44 + function getExistingHashes() {
45 + const existing = Cookies.get( 'passster' );
46 + if ( ! existing ) {
47 + return [];
48 + }
49 + // Support both old (single hash) and new (pipe-separated) format
50 + return existing.split( '|' ).filter( h => h.length > 0 );
51 + }
52 +
53 + // Set hashed cookie (appends to existing hashes).
54 + async function setHashedCookie( password, postId ) {
55 + const hash = await hashPassword( password, postId );
56 + if ( hash ) {
57 + const hashes = getExistingHashes();
58 + // Only add if not already present
59 + if ( ! hashes.includes( hash ) ) {
60 + hashes.push( hash );
61 + }
62 + Cookies.set( 'passster', hashes.join( '|' ), {
63 + expires: getDurationBySettings(),
64 + sameSite: 'strict',
65 + } );
66 + }
67 + }
68 +
69 + // Set simple cookie (for captcha) - appends to existing.
70 + function setCookie( value ) {
71 + const hashes = getExistingHashes();
72 + if ( ! hashes.includes( value ) ) {
73 + hashes.push( value );
74 + }
75 + Cookies.set( 'passster', hashes.join( '|' ), {
76 + expires: getDurationBySettings(),
77 + sameSite: 'strict',
78 + } );
79 + }
80 +
81 + // Handle successful unlock.
82 + function handleSuccess( data, $form, redirect ) {
83 + if ( data.redirect ) {
84 + window.location.replace( data.redirect );
85 + return;
86 + }
87 +
88 + if ( ! ps_ajax.unlock_mode ) {
89 + window.location.href = getCacheFriendlyURL();
90 + } else {
91 + $form.find( '.passster-error' ).hide();
92 + if ( data.requires_reload ) {
93 + window.location.href = getCacheFriendlyURL();
94 + return;
95 + }
96 + if ( data.content ) {
97 + let content = data.content;
98 + if ( ps_ajax.shortcodes ) {
99 + $.each( ps_ajax.shortcodes, function( key, value ) {
100 + content = content.replace( key, value );
101 + } );
102 + }
103 + $form.replaceWith( content );
104 + }
105 + if ( redirect ) {
106 + window.location.replace( redirect );
107 + }
108 + }
109 + }
110 +
111 + // Show error.
112 + function showError( $form, message ) {
113 + const $error = $form.find( '.passster-error' );
114 + $error.text( message || 'An error occurred.' );
115 + $error.show().fadeOut( 3500 );
116 + }
117 +
118 + // Check if we have an unlock link.
119 + if ( ps_ajax.link_pass ) {
120 + if ( ! ps_ajax.disable_cookie ) {
121 + if ( ps_ajax.link_pass.length < 25 ) {
122 + setHashedCookie( atob( ps_ajax.link_pass ), ps_ajax.post_id ).then( () => {
123 + window.location.replace( ps_ajax.permalink + '?pts=' + Math.floor( Date.now() / 1000 ) );
124 + } );
125 + } else {
126 + setCookie( ps_ajax.link_pass );
127 + window.location.replace( ps_ajax.permalink + '?pts=' + Math.floor( Date.now() / 1000 ) );
128 + }
129 + }
130 + }
131 +
132 + // Password form submit - using REST API.
133 + $( '.passster-submit' ).on( 'click', async function( e ) {
134 + e.preventDefault();
135 +
136 + const $button = $( this );
137 + const psId = $button.attr( 'data-psid' );
138 + const $form = $( '#' + psId );
139 + const $input = $form.find( '.passster-password' );
140 + const postId = parseInt( $button.attr( 'data-post-id' ) ) || ps_ajax.post_id;
141 +
142 + // Validate form.
143 + const formEl = $form.find( 'form' )[ 0 ];
144 + if ( formEl && ! formEl.checkValidity() ) {
145 + formEl.reportValidity();
146 + return;
147 + }
148 +
149 + // Get form data.
150 + const password = $input.val();
151 + const type = ( $input.attr( 'data-protection-type' ) || 'password' ).replace( /-/g, '_' );
152 + const areaId = parseInt( $input.attr( 'data-area' ) ) || 0;
153 + const listId = parseInt( $input.attr( 'data-list' ) ) || 0;
154 + const lists = $input.attr( 'data-lists' ) || '';
155 + const protection = $input.attr( 'data-protection' ) || '';
156 + const acf = $button.attr( 'data-acf' ) || '';
157 + const redirect = $button.attr( 'data-redirect' ) || '';
158 + const termId = parseInt( $button.attr( 'data-term-id' ) ) || 0;
159 + const postType = $button.attr( 'data-post-type' ) || '';
160 +
161 + // Get block ID from parent wrapper if exists.
162 + const $wrapper = $form.closest( '.passster-protected-content' );
163 + const blockId = $wrapper.length ? $wrapper.attr( 'data-block-id' ) : '';
164 +
165 + $form.find( '.ps-loader' ).css( 'display', 'block' );
166 +
167 + try {
168 + const response = await fetch( restUrl + '/unlock', {
169 + method: 'POST',
170 + credentials: 'same-origin',
171 + headers: { 'Content-Type': 'application/json' },
172 + body: JSON.stringify( {
173 + password,
174 + type,
175 + post_id: postId,
176 + area_id: areaId,
177 + block_id: blockId,
178 + list_id: listId,
179 + lists,
180 + protection,
181 + acf,
182 + redirect,
183 + term_id: termId,
184 + post_type: postType,
185 + } ),
186 + } );
187 +
188 + const data = await response.json();
189 + $form.find( '.ps-loader' ).css( 'display', 'none' );
190 + if ( data.success ) {
191 + handleSuccess( data, $form, redirect );
192 + } else {
193 + showError( $form, data.error );
194 + $input.val( '' );
195 + }
196 + } catch ( err ) {
197 + $form.find( '.ps-loader' ).css( 'display', 'none' );
198 + showError( $form, 'An error occurred. Please try again.' );
199 + }
200 + } );
201 +
202 + // Captcha validation via REST API.
203 + async function validateCaptcha( token, type, $form, psId, areaId, redirect, protection ) {
204 + try {
205 + const response = await fetch( restUrl + '/captcha', {
206 + method: 'POST',
207 + credentials: 'same-origin',
208 + headers: { 'Content-Type': 'application/json' },
209 + body: JSON.stringify( {
210 + token,
211 + type,
212 + post_id: ps_ajax.post_id,
213 + area_id: areaId,
214 + redirect,
215 + protection: protection || '',
216 + } ),
217 + } );
218 +
219 + const data = await response.json();
220 +
221 + if ( data.success ) {
222 + handleSuccess( data, $form, redirect );
223 + } else {
224 + showError( $form, data.error );
225 + }
226 + } catch ( err ) {
227 + showError( $form, 'Captcha validation failed.' );
228 + }
229 + }
230 +
231 + // Recaptcha v2
232 + if ( $( '.recaptcha-form-v2' ).length > 0 && window.grecaptcha ) {
233 + grecaptcha.ready( function() {
234 + grecaptcha.render( 'ps-recaptcha-v2', {
235 + sitekey: ps_ajax.recaptcha_key,
236 + callback( token ) {
237 + const psId = $( '.recaptcha-v2-submit' ).attr( 'data-psid' );
238 + const $form = $( '#' + psId );
239 + const $btn = $form.find( '.recaptcha-v2-submit' );
240 + const areaId = parseInt( $btn.attr( 'data-area' ) ) || 0;
241 + const redirect = $btn.attr( 'data-redirect' ) || '';
242 + const protection = $btn.attr( 'data-protection' ) || '';
243 +
244 + validateCaptcha( token, 'recaptcha_v2', $form, psId, areaId, redirect, protection );
245 + },
246 + } );
247 + } );
248 + }
249 +
250 + // ReCaptcha v3
251 + $( '.recaptcha-form' ).on( 'submit', function( event ) {
252 + event.preventDefault();
253 +
254 + const $thisForm = $( this );
255 + const $btn = $thisForm.find( '.passster-submit-recaptcha' );
256 + const psId = $btn.attr( 'data-psid' );
257 + const $form = $( '#' + psId );
258 + const areaId = parseInt( $btn.attr( 'data-area' ) ) || 0;
259 + const redirect = $btn.attr( 'data-redirect' ) || '';
260 + const protection = $btn.attr( 'data-protection' ) || '';
261 +
262 + if ( window.grecaptcha ) {
263 + grecaptcha.ready( function() {
264 + grecaptcha.execute( ps_ajax.recaptcha_key, { action: 'validate_input' } ).then( function( token ) {
265 + validateCaptcha( token, 'recaptcha_v3', $form, psId, areaId, redirect, protection );
266 + } );
267 + } );
268 + }
269 + } );
270 +
271 + // hCaptcha
272 + $( '.hcaptcha-form' ).on( 'submit', function( event ) {
273 + event.preventDefault();
274 +
275 + const $thisForm = $( this );
276 + const $btn = $thisForm.find( '.passster-submit-recaptcha' );
277 + const psId = $btn.attr( 'data-psid' );
278 + const $form = $( '#' + psId );
279 + const areaId = parseInt( $btn.attr( 'data-area' ) ) || 0;
280 + const redirect = $btn.attr( 'data-redirect' ) || '';
281 + const protection = $btn.attr( 'data-protection' ) || '';
282 +
283 + if ( window.hcaptcha ) {
284 + hcaptcha.execute( { async: true } )
285 + .then( ( { response } ) => {
286 + validateCaptcha( response, 'hcaptcha', $form, psId, areaId, redirect, protection );
287 + } )
288 + .catch( ( err ) => {
289 + showError( $form, err.message || 'hCaptcha error' );
290 + } );
291 + }
292 + } );
293 +
294 + // Turnstile
295 + let turnstileToken = '';
296 + if ( $( '.turnstile-form' ).length > 0 && window.turnstile ) {
297 + window.turnstile.ready( function() {
298 + window.turnstile.render( '.passster-turnstile', {
299 + sitekey: ps_ajax.turnstile_key,
300 + callback( token ) {
301 + turnstileToken = token;
302 + },
303 + } );
304 + } );
305 +
306 + $( '.turnstile-form' ).on( 'submit', function( event ) {
307 + event.preventDefault();
308 +
309 + const $thisForm = $( this );
310 + const $btn = $thisForm.find( '.passster-submit-turnstile' );
311 + const psId = $btn.attr( 'data-psid' );
312 + const $form = $( '#' + psId );
313 + const areaId = parseInt( $btn.attr( 'data-area' ) ) || 0;
314 + const redirect = $btn.attr( 'data-redirect' ) || '';
315 + const protection = $btn.attr( 'data-protection' ) || '';
316 +
317 + if ( ! turnstileToken ) {
318 + showError( $form, 'Please complete the verification.' );
319 + return;
320 + }
321 +
322 + validateCaptcha( turnstileToken, 'turnstile', $form, psId, areaId, redirect, protection );
323 + } );
324 + }
325 +
326 + // Concurrent logout - using REST API.
327 + $( document ).on( 'click', '#ps-logout', async function() {
328 + try {
329 + const response = await fetch( restUrl + '/logout', {
330 + method: 'POST',
331 + credentials: 'same-origin',
332 + headers: { 'Content-Type': 'application/json' },
333 + } );
334 + const data = await response.json();
335 + if ( data.success ) {
336 + Cookies.set( 'passster', '', { expires: 0, sameSite: 'strict' } );
337 + window.location.href = getCacheFriendlyURL();
338 + }
339 + } catch ( e ) {
340 + window.location.href = getCacheFriendlyURL();
341 + }
342 + } );
343 +} );