1jQuery(document).ready(function ($) { 2 window.wawpOtpAjax = window.wawpOtpAjax || {}; 3 const wawpOtpAjax = window.wawpOtpAjax; 4 5 /* ------------------------------------------------------------ 6 * Per-instance branding helpers (scoped to .wawp-otp-login-form) 7 * ---------------------------------------------------------- */ 8 function hexToRgbA(hex, alpha = 1) { 9 let c; 10 if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) { 11 c = hex.substring(1).split(''); 12 if (c.length === 3) { 13 c = [c[0], c[0], c[1], c[1], c[2], c[2]]; 14 } 15 c = '0x' + c.join(''); 16 return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + alpha + ')'; 17 } 18 return hex; 19 } 20 21 function updateBranding($wrapper, type, identifier, isMagicLink = false) { 22 const $branding = $wrapper.find('.wawp-login-branding'); 23 if (!$branding.length) return; 24 25 const l10n = window.wawpOtpL10n || {}; 26 27 if (!type) { 28 // Restore original stored on the wrapper 29 const orig = $wrapper.data('wawp-branding-orig'); 30 if (orig) { 31 $branding.find('.wawp-logo-container').html(orig.logo); 32 $branding.find('.wawp-login-title').text(orig.title); 33 $branding.find('.wawp-login-description').html(orig.desc); 34 } 35 return; 36 } 37 38 let iconClass = '', titleText = '', descHtml = '', themeClass = '', customColor = ''; 39 40 if (type === 'whatsapp') { 41 iconClass = 'ri-whatsapp-line'; titleText = l10n.checkWhatsApp || 'Check your WhatsApp'; 42 descHtml = (l10n.sentTo || 'We\u2019ve sent you a 6-digit code to') + ' <br><b>' + identifier + '</b>'; 43 themeClass = 'wawp-otp-theme-whatsapp'; 44 customColor = (window.wawpOtpAjax && window.wawpOtpAjax.button_colors) ? window.wawpOtpAjax.button_colors.whatsapp : '#25d366'; 45 } else if (type === 'firebase') { 46 iconClass = 'ri-message-3-line'; titleText = l10n.checkSMS || 'Check your SMS'; 47 descHtml = (l10n.sentTo || 'We\u2019ve sent you a 6-digit code to') + ' <br><b>' + identifier + '</b>'; 48 themeClass = 'wawp-otp-theme-sms'; 49 customColor = (window.wawpOtpAjax && window.wawpOtpAjax.button_colors) ? window.wawpOtpAjax.button_colors.firebase : '#3b82f6'; 50 } else if (type === 'email') { 51 iconClass = 'ri-mail-line'; titleText = l10n.checkEmail || 'Check your Email'; 52 descHtml = isMagicLink 53 ? ((l10n.magicLinkSent || 'We\u2019ve sent you a secure login link to') + ' <br><b>' + identifier + '</b>') 54 : ((l10n.sentTo || 'We\u2019ve sent you a 6-digit code to') + ' <br><b>' + identifier + '</b>'); 55 themeClass = 'wawp-otp-theme-email'; 56 customColor = (window.wawpOtpAjax && window.wawpOtpAjax.button_colors) ? window.wawpOtpAjax.button_colors.email_otp : '#ef4444'; 57 } 58 59 $branding.find('.wawp-logo-container').html('<div class="wawp-icon-wrapper ' + themeClass + '"><i class="wawp-otp-icon ' + iconClass + '"></i></div>'); 60 if (customColor) { 61 setTimeout(function () { 62 const $ic = $branding.find('.wawp-otp-icon'); 63 if ($ic.length) { 64 $ic.css({ 'background-color': customColor, 'color': '#ffffff', 65 'box-shadow': '0 0 0 10px ' + hexToRgbA(customColor, 0.15), 66 'animation': 'wawp-pulse-scale 2s infinite ease-in-out' }); 67 } 68 }, 50); 69 } 70 $branding.find('.wawp-login-title').text(titleText); 71 $branding.find('.wawp-login-description').html(descHtml); 72 } 73 74 // Store original branding per wrapper on DOM ready 75 $('.wawp-otp-login-form').each(function () { 76 const $w = $(this); 77 const $b = $w.find('.wawp-login-branding'); 78 $w.data('wawp-branding-orig', { 79 logo: $b.find('.wawp-logo-container').html(), 80 title: $b.find('.wawp-login-title').text(), 81 desc: $b.find('.wawp-login-description').html() 82 }); 83 }); 84 85 /* ------------------------------------------------------------ 86 * Email Autocomplete 87 * ---------------------------------------------------------- */ 88 const emailDomains = wawpOtpAjax.email_domains || ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com']; 89 90 function setupEmailAutocomplete($input) {
91 const $wrapper = $('<div class="wawp-email-autocomplete-wrapper"></div>'); 92 $input.wrap($wrapper); 93 const $list = $('<ul class="wawp-email-suggestions"></ul>'); 94 $input.after($list); 95 96 $input.on('input', function () { 97 const val = $(this).val(); 98 $list.empty().hide(); 99 if (!val || !val.includes('@')) return; 100 101 const [user, domainPart] = val.split('@'); 102 if (!user) return; 103 104 const matches = emailDomains.filter(d => d.startsWith(domainPart)); 105 if (matches.length > 0) { 106 matches.forEach(m => { 107 const $li = $('<li>' + user + '@' + m + '</li>'); 108 $li.on('click', function () { 109 $input.val($(this).text()).trigger('input'); 110 $list.hide(); 111 }); 112 $list.append($li); 113 }); 114 $list.show(); 115 } 116 }); 117 118 $(document).on('click', function (e) { 119 if (!$(e.target).closest('.wawp-email-autocomplete-wrapper').length) { 120 $list.hide(); 121 } 122 }); 123 } 124 125 $('.js-wawp-email-input').each(function () { setupEmailAutocomplete($(this)); }); 126 127 /* ------------------------------------------------------------ 128 * Cooldown Guard (Persistent & Incremental) 129 * ---------------------------------------------------------- */ 130 const STORAGE_KEY = 'wawp_otp_cooldown'; 131 let cooldownState = { phone: { attempts: 0, expires: 0 }, email: { attempts: 0, expires: 0 } }; 132 try { 133 const stored = localStorage.getItem(STORAGE_KEY); 134 if (stored) cooldownState = JSON.parse(stored); 135 } catch (e) { 136 console.error('AWP OTP: Corrupt storage reset', e); 137 localStorage.removeItem(STORAGE_KEY); 138 } 139 140 function saveCooldownState() { 141 localStorage.setItem(STORAGE_KEY, JSON.stringify(cooldownState)); 142 } 143 144 function runTimer(method, $btn, expiryTime) { 145 const $container = $btn.closest('.wawp-action-buttons'); 146 let $timer = $container.find('.wawp-resend-timer'); 147 148 if (!$timer.length) { 149 $timer = $('<div class="wawp-resend-timer" aria-live="polite" style="margin-top:10px;font-size:13px;opacity:0.9;text-align:center;width:100%;"></div>'); 150 $container.append($timer); 151 } 152 153 function label(sec) { 154 return (window.wawpOtpL10n && wawpOtpL10n.resendIn) ? wawpOtpL10n.resendIn.replace('%s', sec) : 'Resend in ' + sec + 's'; 155 } 156 157 $btn.prop('disabled', true); 158 159 function update() { 160 const now = Date.now(); 161 const remaining = Math.ceil((expiryTime - now) / 1000); 162 163 if (remaining <= 0) { 164 clearInterval($timer.data('interval')); 165 $btn.prop('disabled', false); 166 $timer.remove(); 167 cooldownState[method].expires = 0; 168 saveCooldownState(); 169 } else { 170 $timer.text(label(remaining)); 171 } 172 } 173 174 update(); // Run immediately 175 176 // Clear any existing interval on this element 177 if ($timer.data('interval')) clearInterval($timer.data('interval')); 178 179 const interval = setInterval(update, 1000); 180 $timer.data('interval', interval); 181 } 182 183 function checkCooldown(method, $btn) { 184 const state = cooldownState[method]; 185 if (!state || !state.expires) return; 186 187 // If no specific button passed, try to find the standard ones (for page load) 188 if (!$btn || !$btn.length) { 189 if (method === 'phone') { 190 $btn = $('.wawp-request-btn[data-method="whatsapp"], .wawp-request-btn[data-method="firebase"], .wawp-resend-otp-phone'); 191 } else if (method === 'email') { 192 $btn = $('.js-wawp-request-otp-email, .js-wawp-resend-otp-email'); 193 } 194 } 195 196 const now = Date.now(); 197 if (state.expires > now) { 198 $btn.each(function () { 199 runTimer(method, $(this), state.expires); 200 }); 201 } else { 202 // Reset attempts if expired long ago (e.g. 5 mins) 203 if (now > state.expires + 300000) { 204 state.attempts = 0; 205 saveCooldownState(); 206 } 207 } 208 } 209 210 function startCooldown(method, $btn) { 211 const state = cooldownState[method]; 212 state.attempts = (state.attempts || 0) + 1; 213 214 // Incremental: 30s, 60s, 120s, ... max 5 mins 215 const initial = parseInt(wawpOtpAjax.initial_cooldown) || 30; 216 const max = parseInt(wawpOtpAjax.max_cooldown) || 300; 217 const seconds = Math.min(initial * Math.pow(2, state.attempts - 1), max); 218 219 state.expires = Date.now() + (seconds * 1000); 220 saveCooldownState(); 221 222 runTimer(method, $btn, state.expires); 223 } 224 225 226 /* ------------------------------------------------------------ 227 * Helpers 228 * ---------------------------------------------------------- */ 229 function withSpinner($btn, fn) { 230 const originalHtml = $btn.html(); 231 $btn.data('original-html', originalHtml).prop('disabled', true).addClass('is-loading').html('<i class="ri-loader-5-line"></i>'); 232 233 // Start progress timeline in the message box area 234 const $form = $btn.closest('form'); 235 const $msgBox = $form.find('.wawp-login-message-phone, .js-wawp-msg-box-email'); 236 237 return fn(() => { 238 $btn.prop('disabled', false).removeClass('is-loading').html($btn.data('original-html')); 239 }); 240 } 241 242 function setMessage($el, text, ok = true) { 243 if (!$el || !$el.length) return; 244 $el.html('<p class="' + (ok ? 'success' : 'error') + '">
244' + text + '</p>'); 245 } 246 247 async function wawpRest(op, data = {}) { 248 if (!wawpOtpAjax.rest_url) { 249 return $.post(wawpOtpAjax.ajax_url, { 250 action: 'wawp_request_otp', 251 nonce: wawpOtpAjax.nonce, 252 ...data, 253 op: op 254 }).promise(); 255 } 256 257 const headers = { 'Content-Type': 'application/json' }; 258 if (wawpOtpAjax.nonce) { 259 headers['X-WP-Nonce'] = wawpOtpAjax.nonce; 260 } 261 262 try { 263 let response = await fetch(wawpOtpAjax.rest_url + '/' + op, { 264 method: 'POST', 265 headers: headers, 266 body: JSON.stringify(data) 267 }); 268 269 let result = await response.json().catch(() => null); 270 271 // ð¡ï¸ Auto-Retry on Stale Cache / Nonce Cookie Check Failure 272 if (!response.ok && result && (result.code === 'rest_cookie_invalid_nonce' || result.message === 'Cookie check failed' || response.status === 403)) { 273 const retryResponse = await fetch(wawpOtpAjax.rest_url + '/' + op, { 274 method: 'POST', 275 headers: { 'Content-Type': 'application/json' }, 276 body: JSON.stringify(data) 277 }); 278 const retryResult = await retryResponse.json().catch(() => null); 279 if (retryResponse.ok && retryResult) { 280 return retryResult; 281 } 282 283 if (wawpOtpAjax.ajax_url) { 284 return $.post(wawpOtpAjax.ajax_url, { 285 action: 'wawp_request_otp', 286 nonce: wawpOtpAjax.nonce, 287 ...data, 288 op: op 289 }).promise(); 290 } 291 } 292 293 if (!response.ok || !result) { 294 const err = new Error(result?.message || 'REST Request Failed'); 295 err.data = result?.data || result; 296 throw err; 297 } 298 return result; 299 } catch (fetchErr) { 300 if (wawpOtpAjax.ajax_url && (fetchErr.message?.includes('Cookie check failed') || fetchErr.message?.includes('REST Request Failed'))) { 301 return $.post(wawpOtpAjax.ajax_url, { 302 action: 'wawp_request_otp', 303 nonce: wawpOtpAjax.nonce, 304 ...data, 305 op: op 306 }).promise(); 307 } 308 throw fetchErr; 309 } 310 } 311 312 /* ------------------------------------------------------------ 313 * Firebase SDK Integration 314 * ---------------------------------------------------------- */ 315 let firebaseLoading = false; 316 let confirmationResult = null; 317 318 async function resetFirebaseVerifier(force = false) { 319 if (window.recaptchaVerifier) { 320 try { 321 window.recaptchaVerifier.clear(); 322 } catch (e) { 323 console.warn('Error clearing verifier:', e); 324 } 325 window.recaptchaVerifier = null; 326 } 327 328 const container = document.getElementById('wawp-firebase-recaptcha-phone'); 329 if (container) { 330 const parent = container.parentNode; 331 if (parent) { 332 const newContainer = document.createElement('div'); 333 newContainer.id = 'wawp-firebase-recaptcha-phone'; 334 newContainer.className = container.className; 335 parent.replaceChild(newContainer, container); 336 337 $(newContainer).removeClass('wawp-hide').css({ 338 'position': 'absolute', 339 'width': '1px', 340 'height': '1px', 341 'padding': '0', 342 'margin': '-1px', 343 'overflow': 'hidden', 344 'clip': 'rect(0, 0, 0, 0)', 345 'border': '0', 346 'opacity': '0', 347 'pointer-events': 'none' 348 }); 349 } else { 350 container.innerHTML = ''; 351 } 352 } 353 354 await initFirebaseVerifier(force); 355 } 356 357 async function ensureFirebase(force = false) { 358 if (typeof firebase !== 'undefined' && firebase.auth) { 359 if (!window.recaptchaVerifier) { 360 await resetFirebaseVerifier(force); 361 } 362 return true; 363 } 364 365 // Fallback: Dynamic loading if not enqueued (should happen rarely now) 366 if (firebaseLoading) return new Promise((resolve, reject) => { 367 const i = setInterval(() => { 368 if (typeof firebase !== 'undefined' && firebase.auth) { 369 clearInterval(i); 370 resolve(true); 371 } 372 }, 100); 373 setTimeout(() => { 374 clearInterval(i); 375 reject(new Error('Firebase SDK initialization timed out.')); 376 }, 15000); 377 }); 378 firebaseLoading = true; 379 380 const scripts = [
381 'https://www.gstatic.com/firebasejs/9.1.1/firebase-app-compat.js', 382 'https://www.gstatic.com/firebasejs/9.1.1/firebase-auth-compat.js' 383 ]; 384 385 for (const src of scripts) { 386 await new Promise((resolve, reject) => { 387 const s = document.createElement('script'); 388 s.src = src; 389 s.onload = resolve; 390 s.onerror = () => reject(new Error('Failed to load Firebase SDK from Google servers. Please check your network connection.')); 391 document.head.appendChild(s); 392 }); 393 } 394 395 await resetFirebaseVerifier(force); 396 return true; 397 } 398 399 async function initFirebaseVerifier(force = false) { 400 // Guard: If the container doesn't exist (e.g. page without login form), skip init 401 const container = document.getElementById('wawp-firebase-recaptcha-phone'); 402 if (!container) return; 403 404 // Skip initialization if the parent element is hidden (e.g. inside an inactive tab) unless forced 405 if (!force && $(container).parent().closest(':hidden').length > 0) { 406 return; 407 } 408 409 const config = wawpOtpAjax.firebase_config; 410 if (!config) { console.error('Firebase config missing'); return; } 411 412 // Mobile fallback: map api_key to apiKey if missing 413 if (!config.apiKey && config.api_key) { 414 config.apiKey = config.api_key; 415 } 416 417 if (!config.apiKey && !config.api_key) { 418 console.error('AWP OTP: Firebase API Key missing in localized config object. Please check your Firebase Sender settings and ensure a project is set as Active.'); 419 return; 420 } 421 422 // Clean the config object to only include valid Firebase parameters 423 const firebaseConfig = { 424 apiKey: config.apiKey || config.api_key, 425 authDomain: config.authDomain, 426 projectId: config.projectId, 427 storageBucket: config.storageBucket, 428 messagingSenderId: config.messagingSenderId, 429 appId: config.appId, 430 measurementId: config.measurementId 431 }; 432 433 // Use a NAMED app to avoid conflicts with other plugins (like Google One Tap) 434 let app = null; 435 try { 436 app = firebase.app('wawp_firebase_app'); 437 } catch (e) { 438 // App not initialized yet 439 app = firebase.initializeApp(firebaseConfig, 'wawp_firebase_app'); 440 } 441 442 // Always ensure the container styles are properly set up (visible to DOM but screen-reader hidden) 443 if (container) { 444 $(container).removeClass('wawp-hide').css({ 445 'position': 'absolute', 446 'width': '1px', 447 'height': '1px', 448 'padding': '0', 449 'margin': '-1px', 450 'overflow': 'hidden', 451 'clip': 'rect(0, 0, 0, 0)', 452 'border': '0', 453 'opacity': '0', 454 'pointer-events': 'none' 455 }); 456 } 457 458 if (!window.recaptchaVerifier) { 459 try { 460 // Pass the specific app instance 461 window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('wawp-firebase-recaptcha-phone', { 462 'size': 'invisible', 463 'callback': (response) => { /* reCAPTCHA solved */ } 464 }, app); 465 466 // Render it immediately to warm up 467 await window.recaptchaVerifier.render(); 468 } catch (e) { console.warn('Recaptcha init error', e); } 469 } 470 } 471 472 // Highly-optimized async pre-loading: warm up and pre-load Firebase scripts and verifier immediately on page load in the background 473 if (wawpOtpAjax.firebase_enabled) { 474 ensureFirebase().catch(e => console.warn('Pre-loading Firebase failed asynchronously', e)); 475 } 476 477 /* ------------------------------------------------------------ 478 * ReCAPTCHA (V2/V3) 479 * ---------------------------------------------------------- */ 480 var recaptchaWidgets = {}; 481 let recaptchaInitAttempts = 0; 482 function initRecaptcha(callback) { 483 if (!wawpOtpAjax.recaptcha_enabled || wawpOtpAjax.recaptcha_enabled === '0') { 484 if (typeof callback === 'function') callback(); 485 return; 486 } 487 488 if (typeof grecaptcha === 'undefined' || typeof grecaptcha.render !== 'function') { 489 recaptchaInitAttempts++; 490 if (recaptchaInitAttempts > 10) { 491 console.warn('AWP OTP: Google ReCAPTCHA script not found. Proceeding without it.'); 492 if (typeof callback === 'function') callback(); 493 return; 494 } 495 setTimeout(() => initRecaptcha(callback), 300); 496 return; 497 } 498 499 grecaptcha.ready(function () { 500 $('.wawp-recaptcha-container').each(function () { 501 var id = $(this).attr('id'); 502 if (id === 'wawp-recaptcha-signup') return; // Handled by wawp-signup.js 503 504 if (id && recaptchaWidgets[id] === undefined) { 505 var isCheckbox = wawpOtpAjax.recaptcha_type === 'v2_checkbox'; 506 var params = { 507 'sitekey': wawpOtpAjax.recaptcha_site_key, 508 'size': isCheckbox ? 'normal' : 'invisible', 509 'badge': wawpOtpAjax.is_rtl ? 'bottomleft' : 'bottomright', 510 'callback': function (token) { 511 var cb = $('#' + id).data('awp-recaptcha-cb'); 512 if (cb) { 513 cb(token); 514 $('#' + id).data('awp-recaptcha-cb', null); 515 } 516 } 517 }; 518 try { 519 recaptchaWidgets[id] = grecaptcha.render(id, params); 520 } catch (e) { 521 console.warn('AWP OTP: ReCAPTCHA render error for', id, e); 522 } 523 } 524 }); 525 if (typeof callback === 'function') callback(); 526 }); 527 } 528 529 // Try init on ready and load 530 initRecaptcha(); 531 $(window).on('load', function () { initRecaptcha(); }); 532 $(document).on('wawp_recaptcha_init', function () { initRecaptcha(); }); 533 534 function withRecaptcha(action, onSuccess, onError, $contextForm) { 535 if (!wawpOtpAjax.recaptcha_enabled || !wawpOtpAjax.recaptcha_site_key || wawpOtpAjax.recaptcha_enabled === '0') return onSuccess(null); 536 537 let called = false;
538 const onceSuccess = (token) => { 539 if (called) return; 540 called = true; 541 clearTimeout(safetyTimeout); 542 onSuccess(token); 543 }; 544 545 const safetyTimeout = setTimeout(() => { 546 console.warn('AWP OTP: reCAPTCHA timed out for ' + action); 547 onceSuccess(null); 548 }, 6000); 549 550 initRecaptcha(function () { 551 // Try the new unified container first 552 var containerId = 'wawp-recaptcha-login'; 553 var widgetId = recaptchaWidgets[containerId]; 554 555 // Fallback or override by context form 556 if (widgetId === undefined && $contextForm && $contextForm.length) { 557 var $c = $contextForm.find('.wawp-recaptcha-container'); 558 if ($c.length) { 559 var cid = $c.attr('id'); 560 if (cid && recaptchaWidgets[cid] !== undefined) { 561 widgetId = recaptchaWidgets[cid]; 562 containerId = cid; 563 } 564 } 565 } 566 567 if (widgetId !== undefined) { 568 var isCheckbox = wawpOtpAjax.recaptcha_type === 'v2_checkbox'; 569 var response = grecaptcha.getResponse(widgetId); 570 571 try { 572 if (isCheckbox) { 573 if (response) { 574 onceSuccess(response); 575 } else { 576 setMessage($contextForm.find('.wawp-login-message-phone, .js-wawp-msg-box-email'), window.wawpOtpL10n.pleaseSolveRecaptcha || 'Please solve the reCAPTCHA.', false); 577 // We don't call onceSuccess(null) here yet, let user solve it and click again? 578 // Actually, withRecaptcha is called on button click. 579 } 580 } else { 581 // Invisible 582 if (response) { 583 grecaptcha.reset(widgetId); 584 } 585 $('#' + containerId).data('awp-recaptcha-cb', onceSuccess); 586 grecaptcha.execute(widgetId); 587 } 588 } catch (e) { 589 console.error('AWP OTP: reCAPTCHA execute error', e); 590 onceSuccess(null); 591 } 592 } else { 593 console.warn('AWP OTP: reCAPTCHA widget NOT rendered for', action); 594 onceSuccess(null); 595 } 596 }); 597 } 598 599 /* ------------------------------------------------------------ 600 * Tabs & UI Navigation (instance-scoped) 601 * ---------------------------------------------------------- */ 602 $(document).on('click', '.wawp-tab', function () { 603 const $tab = $(this); 604 const tab = $tab.data('tab'); 605 const $wrapper = $tab.closest('.wawp-otp-login-form'); 606 $wrapper.find('.wawp-tab, .wawp-tab-pane').removeClass('active'); 607 $tab.addClass('active'); 608 $wrapper.find('#' + tab).addClass('active'); 609 610 if (tab.indexOf('phone_login') === 0 && wawpOtpAjax.firebase_enabled) { 611 ensureFirebase().catch(e => console.warn('Pre-loading Firebase on tab switch failed', e)); 612 } 613 }); 614 615 /* ------------------------------------------------------------ 616 * Phone Login Flow (instance-scoped) 617 * ---------------------------------------------------------- */ 618 $(document).on('click', '.wawp-request-btn', function () { 619 const $btn = $(this); 620 const $form = $btn.closest('form'); 621 const $wrapper = $btn.closest('.wawp-otp-login-form'); 622 const method = $btn.data('method'); 623 const phone = $form.find('[name="phone"]').val().trim(); 624 const $msgBox = $form.find('.wawp-login-message-phone'); 625 626 if (!phone) return setMessage($msgBox, wawpOtpAjax.emptyFields, false); 627 628 // Cooldown check 629 const cooldownMethod = 'phone'; 630 const timerState = cooldownState[cooldownMethod]; 631 if (timerState && timerState.expires > Date.now()) { 632 // Highlight the timer if active 633 const $container = $btn.closest('.wawp-action-buttons'); 634 const $timer = $container.find('.wawp-resend-timer'); 635 if ($timer.length) { 636 $timer.removeClass('wawp-timer-alert'); 637 void $timer[0].offsetWidth; // trigger reflow 638 $timer.addClass('wawp-timer-alert'); 639 } 640 return; 641 } 642 643 withSpinner($btn, async (finish) => { 644 $msgBox.html(''); 645 646 if (method === 'firebase') { 647 // Pre-check if user exists in WordPress before calling Firebase 648 wawpRest('request', { 649 login_method: 'phone_firebase', 650 phone: phone 651 }).then(async resp => { 652 if (resp.success) { 653 await ensureFirebase(true); 654 const $phoneInput = $form.find('[name="phone"]')[0]; 655 const iti = ($phoneInput && window.intlTelInputGlobals && window.intlTelInputGlobals.getInstance) ? window.intlTelInputGlobals.getInstance($phoneInput) : null; 656 let fullPhone = ''; 657 if (iti) { 658 const cd = iti.getSelectedCountryData() || {}; 659 const dial = cd.dialCode ? String(cd.dialCode) : ''; 660 const digits = phone.replace(/\D+/g, ''); 661 const looksInternational = dial && digits.indexOf(dial) === 0; 662 let fullDigits = looksInternational ? digits : (dial + digits); 663 664 // Auto-Correction for Benin (+229) 10-digit upgrade (October 2024: adds "01" after dial) 665 if (dial === '229') { 666 const numPart = fullDigits.startsWith('229') ? fullDigits.substring(3) : fullDigits; 667 if (numPart.length === 8) { 668 fullDigits = '22901' + numPart; 669 } 670 } 671 672 fullPhone = '+' + fullDigits; 673 } else { 674 fullPhone = phone.startsWith('+') ? phone : '+' + phone; 675 } 676 677 // Bulletproof Auto-Correction: If number is Benin (+229) and length is 12 (+229 + 8 digits), add "01" 678 if (fullPhone.startsWith('+229') && fullPhone.length === 12) { 679 const numberPart = fullPhone.substring(4); // Skip "+229" 680 fullPhone = '+22901' + numberPart; 681 } 682 683 // Use the NAMED app instance 684 const app = firebase.app('wawp_firebase_app'); 685 686 app.auth().signInWithPhoneNumber(fullPhone, window.recaptchaVerifier) 687 .then((result) => { 688 confirmationResult = result; 689 // Log the request to history 690 wawpRest('log_firebase', { phone: phone }); 691 finish(); 692 updateBranding($wrapper, 'firebase', phone); 693 showOtpStep($wrapper, $form, 'phone', phone);
694 startCooldown('phone', $form.find('.wawp-resend-otp-phone')); 695 }).catch(async (error) => { 696 finish(); 697 let userMsg = error.message; 698 if (error.code === 'auth/network-request-failed' || String(error).includes('network-request-failed')) { 699 userMsg = (window.wawpOtpL10n && window.wawpOtpL10n.firebaseNetworkError) 700 ? window.wawpOtpL10n.firebaseNetworkError 701 : 'A network error occurred. If you are using Safari or Brave, please disable tracking protection (Shields) or try another browser.'; 702 } 703 setMessage($msgBox, userMsg, false); 704 await resetFirebaseVerifier(); 705 wawpRest('log_auth_failure', { 706 phone: phone, 707 method: 'firebase', 708 error: error.message || String(error) 709 }).catch(e => console.warn('Logging failure error', e)); 710 }); 711 } else { 712 finish(); 713 const errorMsg = (resp.data && resp.data.message) ? resp.data.message : (resp.data || (typeof resp === 'string' ? resp : 'Unknown error')); 714 if (resp.data && resp.data.redirect_to_signup) { 715 window.location.href = resp.data.redirect_to_signup; 716 } else { 717 setMessage($msgBox, errorMsg, false); 718 } 719 } 720 }).catch((err) => { 721 finish(); 722 if (err.data && err.data.redirect_to_signup) { 723 window.location.href = err.data.redirect_to_signup; 724 return; 725 } 726 setMessage($msgBox, err.message || 'Server error. Please try again later.', false); 727 wawpRest('log_auth_failure', { 728 phone: phone, 729 method: 'firebase', 730 error: err.message || String(err) 731 }).catch(e => console.warn('Logging failure error', e)); 732 }); 733 } else { 734 // WhatsApp 735 withRecaptcha('phone_' + method, (token) => { 736 wawpRest('request', { 737 login_method: 'phone_' + method, 738 phone: phone, 739 'g-recaptcha-response': token 740 }).then(resp => { 741 finish(); 742 if (resp && resp.success) { 743 const authType = resp.data.whatsapp_auth_type || 'otp'; 744 745 if (authType === 'list') { 746 // 1. Hide messy elements 747 $wrapper.find('.wawp-tabs').hide(); 748 $wrapper.find('.wawp-remember-me').addClass('wawp-hide-force'); 749 $wrapper.find('.wawp-login-branding').hide(); 750 $form.find('.wawp-phone-group, .wawp-action-buttons, .wawp-otp-group-phone, .wawp-verify-row-phone, .wawp-separator, .wawp-switch-form').addClass('wawp-hide-force'); 751 $msgBox.empty(); 752 753 // 2. Inject Premium Waiting UI 754 const waitingHtml = ` 755 <div class="wawp-list-waiting-container"> 756 <div class="wawp-list-waiting-icon"> 757 <i class="ri-shield-check-line"></i> 758 </div> 759 <h4 class="wawp-list-waiting-title">${window.wawpOtpL10n.verifyYourIdentity || 'Verify Your Identity'}</h4> 760 <p class="wawp-list-waiting-text">${window.wawpOtpL10n.weSentSecureRequest || 'We sent a secure verification request to'} <b>${phone}</b>.<br>${window.wawpOtpL10n.pleaseCheckDevice || 'Please check your device and approve it to continue.'}</p> 761 <div style="margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px;"> 762 <a href="#" class="js-wawp-back-to-input" style="font-size: 12px; color: #718096; text-decoration: underline;">${window.wawpOtpL10n.editNumberOrMethod || 'Edit number or use another method'}</a> 763 </div> 764 </div> 765 `; 766 $msgBox.after(waitingHtml); 767 768 // 3. Scroll to form if needed 769 $('html, body').animate({ scrollTop: $form.offset().top - 100 }, 500); 770 771 startListPolling(phone, resp.data.otp_transient, $msgBox); 772 } else { 773 setMessage($msgBox, resp.data.message || 'Verification code sent successfully.', true); 774 showOtpStep($wrapper, $form, 'phone', phone); 775 updateBranding($wrapper, 'whatsapp', phone); 776 $form.find('.wawp-verify-row-phone').removeClass('wawp-hide').css('display', 'flex').show(); 777 $btn.hide(); 778 $form.find('.wawp-remember-me').addClass('wawp-hide-force'); 779 if (resp.data && resp.data.otp_transient) { 780 $form.find('.wawp-resend-otp-phone').data('transient', resp.data.otp_transient); 781 $form.find('.wawp-verify-otp-phone').data('transient', resp.data.otp_transient); 782 }
783 startCooldown('phone', $form.find('.wawp-resend-otp-phone')); 784 } 785 } else { 786 const errorMsg = (resp && resp.data && resp.data.message) ? resp.data.message : (resp && resp.data ? resp.data : (typeof resp === 'string' ? resp : 'Request failed.')); 787 if (resp && resp.data && resp.data.redirect_to_signup) { 788 window.location.href = resp.data.redirect_to_signup; 789 } else { 790 setMessage($msgBox, errorMsg, false); 791 } 792 } 793 }).catch((err) => { 794 finish(); 795 if (err.data && err.data.redirect_to_signup) { 796 window.location.href = err.data.redirect_to_signup; 797 return; 798 } 799 setMessage($msgBox, 'Server error: ' + (err.message || 'unknown'), false); 800 }); 801 }, (err) => { finish(); setMessage($msgBox, err, false); }, $form); 802 } 803 }); 804 }); 805 806 // Resend Phone OTP (instance-scoped) 807 $(document).on('click', '.wawp-resend-otp-phone', function () { 808 const $btn = $(this); 809 const $form = $btn.closest('form'); 810 const phone = $form.find('[name="phone"]').val().trim(); 811 const $msgBox = $form.find('.wawp-login-message-phone'); 812 setMessage($msgBox, window.wawpOtpL10n.sendingCode || 'Sending code...', true); 813 814 wawpRest('request', { 815 login_method: 'phone_whatsapp', 816 phone: phone 817 }).then(resp => { 818 if (resp.success) { 819 setMessage($msgBox, window.wawpOtpL10n.codeResent || 'Code resent!'); 820 startCooldown('phone', $btn); 821 } else { 822 const errorMsg = (resp.data && resp.data.message) ? resp.data.message : (resp.data || (typeof resp === 'string' ? resp : 'Unknown error')); 823 setMessage($msgBox, errorMsg, false); 824 } 825 }).catch((err) => { 826 setMessage($msgBox, err.message || 'Failed to resend. Server error.', false); 827 }); 828 }); 829 830 function showOtpStep($wrapper, $form, tab, identifier) { 831 $wrapper.find('.wawp-tabs').hide(); 832 $form.find('.wawp-remember-me').addClass('wawp-hide-force'); 833 if (tab === 'phone') { 834 $form.find('.wawp-phone-group, .wawp-request-btn').hide(); 835 $form.find('.wawp-otp-group-phone, .wawp-otp-sent-message-phone').removeClass('wawp-hide').show(); 836 $form.find('.wawp-verify-row-phone').removeClass('wawp-hide').css('display', 'flex'); 837 838 // Focus 1st digit 839 setTimeout(() => { 840 const $otpInputs = $form.find('.wawp-otp-group-phone .wawp-otp-inputs input'); 841 $otpInputs.val('').removeClass('filled'); 842 $otpInputs.first().focus(); 843 }, 100); 844 checkCooldown('phone', $form.find('.wawp-resend-otp-phone')); 845 } else if (tab === 'email') { 846 $form.find('.js-wawp-email-group').hide(); 847 $form.find('.js-wawp-request-otp-email, .js-wawp-show-password-btn').hide(); 848 $form.find('.js-wawp-otp-group-email, .js-wawp-email-back-link').removeClass('wawp-hide').show(); 849 $form.find('.js-wawp-verify-row-email').removeClass('wawp-hide').css('display', 'flex'); 850 851 // Focus 1st digit 852 setTimeout(() => { 853 const $otpInputs = $form.find('.js-wawp-otp-group-email .wawp-otp-inputs input'); 854 $otpInputs.val('').removeClass('filled'); 855 $otpInputs.first().focus(); 856 }, 100); 857 checkCooldown('email', $form.find('.js-wawp-resend-otp-email')); 858 } 859 } 860 861 // Edit phone (instance-scoped) 862 $(document).on('click', '.wawp-edit-phone', function () { 863 const $form = $(this).closest('form'); 864 const $wrapper = $(this).closest('.wawp-otp-login-form'); 865 $wrapper.find('.wawp-tabs').show(); 866 $form.find('.wawp-phone-group, .wawp-request-btn').show(); 867 $form.find('.wawp-remember-me').removeClass('wawp-hide-force'); 868 $form.find('.wawp-otp-group-phone, .wawp-otp-sent-message-phone').hide(); 869 $form.find('.wawp-verify-row-phone').hide(); 870 $form.find('.wawp-login-message-phone').html(''); 871 updateBranding($wrapper, null); 872 if (typeof firebase !== 'undefined' && firebase.auth) { 873 resetFirebaseVerifier(); 874 } 875 }); 876 877 // Verify Phone OTP (instance-scoped) 878 $(document).on('click', '.wawp-verify-otp-phone', function () { 879 const $btn = $(this); 880 const $form = $btn.closest('form'); 881 const $wrapper = $btn.closest('.wawp-otp-login-form'); 882 const otp = $form.find('[name="otp"]').val().trim(); 883 const phone = $form.find('[name="phone"]').val().trim(); 884 const $msgBox = $form.find('.wawp-login-message-phone'); 885 886 if (!otp) return setMessage($msgBox, window.wawpOtpL10n.pleaseEnterCode || 'Please enter the code.', false); 887 888 withSpinner($btn, (finish) => { 889 if (confirmationResult) { 890 // Firebase Verification 891 confirmationResult.confirm(otp).then((result) => { 892 result.user.getIdToken().then(idToken => { 893 wawpRest('verify_firebase', { 894 idToken: idToken, 895 remember: $form.find('.wawp-remember-phone-cb').is(':checked') ? 1 : 0 896 }).then(resp => { 897 finish(); 898 if (resp && resp.success) window.location.href = resp.data.redirect_url; 899 else { 900 const errorMsg = (resp && resp.data && resp.data.message) ? resp.data.message : (resp && resp.data ? resp.data : (typeof resp === 'string' ? resp : 'Verification failed.')); 901 setMessage($msgBox, errorMsg, false); 902 } 903 }).catch((err) => { 904 finish(); 905 setMessage($msgBox, 'Server error: ' + (err.message || 'unknown'), false); 906 }); 907 }); 908 }).catch(error => { finish(); setMessage($msgBox, error.message, false); }); 909 } else { 910 // WhatsApp Verification 911 wawpRest('verify', { 912 login_method: 'whatsapp_otp', 913 phone: phone, 914 otp: otp, 915 otp_transient: $form.find('.wawp-verify-otp-phone').data('transient') || $form.find('.wawp-resend-otp-phone').data('transient') || '', 916 remember: $form.find('.wawp-remember-phone-cb').is(':checked') ? 1 : 0 917 }).then(resp => { 918 finish(); 919 if (resp && resp.success) window.location.href = resp.data.redirect_url; 920 else { 921 const errorMsg = (resp && resp.data && resp.data.message) ? resp.data.message : (resp && resp.data ? resp.data : (typeof resp === 'string' ? resp : '
921Login failed. Please try again.')); 922 setMessage($msgBox, errorMsg, false); 923 } 924 }).catch((err) => { 925 finish(); 926 setMessage($msgBox, 'Server error: ' + (err.message || 'unknown'), false); 927 }); 928 } 929 }); 930 }); 931 932 /* ------------------------------------------------------------ 933 * Email Login Flow 934 * ---------------------------------------------------------- */ 935 /* ------------------------------------------------------------ 936 * Email Login Flow (Scoped) 937 * ---------------------------------------------------------- */ 938 $(document).on('click', '.js-wawp-show-password-btn', function () { 939 const $form = $(this).closest('.wawp-merged-form'); 940 $form.find('.js-wawp-password-group, .js-wawp-login-submit-password, .js-wawp-email-back-link').removeClass('wawp-hide').show(); 941 $form.find('.js-wawp-show-password-btn, .js-wawp-request-otp-email').hide(); 942 }); 943 944 $(document).on('click', '.js-wawp-back-to-email-options', function (e) { 945 e.preventDefault(); 946 const $form = $(this).closest('.wawp-merged-form'); 947 const $wrapper = $(this).closest('.wawp-otp-login-form'); 948 $form.find('.js-wawp-password-group, .js-wawp-login-submit-password, .js-wawp-otp-group-email, .js-wawp-email-back-link').hide(); 949 $form.find('.js-wawp-verify-row-email').hide(); 950 $form.find('.js-wawp-show-password-btn, .js-wawp-request-otp-email').show(); 951 $form.find('.js-wawp-email-group').show(); 952 $form.find('.wawp-remember-me').removeClass('wawp-hide-force'); 953 $wrapper.find('.wawp-tabs').show(); 954 updateBranding($wrapper, null); 955 }); 956 957 // Email Resend Handler (instance-scoped) 958 $(document).on('click', '.js-wawp-resend-otp-email', function () { 959 const $btn = $(this); 960 const $form = $btn.closest('.wawp-merged-form'); 961 const email = $form.find('.js-wawp-email-input').val(); 962 const $msgBox = $form.find('.js-wawp-msg-box-email'); 963 964 withSpinner($btn, (finish) => { 965 wawpRest('request', { 966 login_method: 'email_otp', 967 email: email 968 }).then(resp => { 969 finish(); 970 if (resp.success) { 971 setMessage($msgBox, window.wawpOtpL10n.codeResent || 'Code resent!'); 972 startCooldown('email', $btn); 973 } else setMessage($msgBox, resp.data, false); 974 }).catch((err) => { 975 finish(); 976 setMessage($msgBox, err.message || 'Server error', false); 977 }); 978 }); 979 }); 980 981 // Email Password Submission (instance-scoped) 982 $(document).on('click', '.js-wawp-login-submit-password', function () { 983 const $btn = $(this); 984 const $form = $btn.closest('.wawp-merged-form'); 985 const login = $form.find('.js-wawp-email-input').val(); 986 const password = $form.find('.js-wawp-password-input').val(); 987 const $msgBox = $form.find('.js-wawp-msg-box-email'); 988 989 if (!login || !password) return setMessage($msgBox, wawpOtpAjax.emptyFields, false); 990 991 withSpinner($btn, (finish) => { 992 withRecaptcha('email_password', (token) => { 993 wawpRest('login_password', { 994 login: login, 995 password: password, 996 remember: $form.find('.wawp-remember-email-cb').is(':checked') ? 1 : 0, 997 'g-recaptcha-response': token 998 }).then(resp => { 999 finish(); 1000 if (resp && resp.success) window.location.href = resp.data.redirect_url; 1001 else { 1002 const errorMsg = (resp && resp.data && resp.data.message) ? resp.data.message : (resp && resp.data ? resp.data : (typeof resp === 'string' ? resp : 'Login failed. Please try again.')); 1003 setMessage($msgBox, errorMsg, false); 1004 const cId = 'wawp-recaptcha-email-merged'; 1005 if (recaptchaWidgets[cId] !== undefined) grecaptcha.reset(recaptchaWidgets[cId]); 1006 } 1007 }).catch((err) => { 1008 finish(); 1009 setMessage($msgBox, 'Server error: ' + (err.message || 'unknown'), false); 1010 }); 1011 }, (err) => { finish(); setMessage($msgBox, err, false); }, $form); 1012 }); 1013 }); 1014 1015 // Email OTP Submission (instance-scoped) 1016 $(document).on('click', '.js-wawp-request-otp-email', function () { 1017 const $btn = $(this); 1018 const $form = $btn.closest('.wawp-merged-form'); 1019 const email = $form.find('.js-wawp-email-input').val(); 1020 const $msgBox = $form.find('.js-wawp-msg-box-email'); 1021 1022 if (!email) return setMessage($msgBox, wawpOtpAjax.emptyFields, false); 1023 1024 withSpinner($btn, (finish) => { 1025 withRecaptcha('email_otp', (token) => { 1026 wawpRest('request', { 1027 login_method: 'email_otp', 1028 email: email, 1029 'g-recaptcha-response': token 1030 }).then(resp => { 1031 finish(); 1032 if (resp && resp.success) { 1033 setMessage($msgBox, resp.data.message || 'Magic link sent! Please check your inbox.', true); 1034 const $wrapper2 = $btn.closest('.wawp-otp-login-form'); 1035 if (resp.data && resp.data.is_magic_link) { 1036 $wrapper2.find('.wawp-tabs').hide(); 1037 $form.find('.wawp-remember-me').addClass('wawp-hide-force'); 1038 $wrapper2.find('.wawp-login-branding').hide(); 1039 $form.find('.js-wawp-email-group, .js-wawp-request-otp-email, .js-wawp-show-password-btn, .wawp-switch-form').hide(); 1040 $msgBox.empty(); 1041 const waitingHtml = ` 1042 <div class="wawp-list-waiting-container"> 1043 <div class="wawp-list-waiting-icon"> 1044 <i class="ri-shield-check-line"></i> 1045 </div> 1046 <h4 class="wawp-list-waiting-title">${window.wawpOtpL10n.verifyYourIdentity || 'Verify Your Identity'}</h4> 1047 <p class="wawp-list-waiting-text">${window.wawpOtpL10n.weSentSecureRequest || 'We sent a secure verification request to'} <b>${email}</b>.<br>${window.wawpOtpL10n.pleaseCheckInbox || 'Please check your inbox and click the link to continue.'}</p> 1048 <div style="margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px;"> 1049 <a href="#" onclick="window.location.reload();" class="js-wawp-back-to-input" style="font-size: 12px; color: #718096; text-decoration: underline;">${window.wawpOtpL10n.editEmailOrMethod || 'Edit email or use another method'}</a> 1050 </div> 1051 </div> 1052 `; 1053 $msgBox.after(waitingHtml); 1054 startListPolling(email, resp.data.otp_transient, $msgBox); 1055 } else { 1056 updateBranding($wrapper2, 'email', email, false); 1057 showOtpStep($wrapper2, $form, 'email', email); 1058 if (resp.data && resp.data.otp_transient) { 1059 $form.find('.js-wawp-resend-otp-email').data('transient', resp.data.otp_transient); 1060 $form.find('.js-wawp-verify-otp-email').data('transient', resp.data.otp_transient); 1061 } 1062 startCooldown('email', $form.find('.js-wawp-resend-otp-email')); 1063 } 1064 } else { 1065 const errorMsg = (resp && resp.data && resp.data.message) ? resp.data.message : (resp && resp.data ? resp.data : (typeof resp === 'string' ? resp : 'Request failed.')); 1066 setMessage($msgBox, errorMsg, false); 1067 const cId = 'wawp-recaptcha-email-merged'; 1068 if (recaptchaWidgets[cId] !== undefined) grecaptcha.reset(recaptchaWidgets[cId]); 1069 } 1070 }).catch((err) => { 1071 finish(); 1072 if (err.data && err.data.redirect_to_signup) { 1073 window.location.href = err.data.redirect_to_signup; 1074 return; 1075 } 1076 setMessage($msgBox, 'Server error: ' + (err.message || 'unknown'), false); 1077 }); 1078 }, (err) => { finish(); setMessage($msgBox, err, false); }, $form); 1079 }); 1080 }); 1081 1082 $(document).on('click', '.js-wawp-verify-otp-email', function () { 1083 const $btn = $(this); 1084 const $form = $btn.closest('.wawp-merged-form'); 1085 const email = $form.find('.js-wawp-email-input').val(); 1086 const otp = $form.find('.js-wawp-otp-input-email').val(); 1087 const $msgBox = $form.find('.js-wawp-msg-box-email'); 1088 1089 withSpinner($btn, (finish) => { 1090 wawpRest('verify', { 1091 login_method: 'email_otp', 1092 email: email, 1093 otp: otp, 1094 otp_transient: $btn.data('transient') || $form.find('.js-wawp-resend-otp-email').data('transient') || '', 1095 remember: $form.find('.wawp-remember-email-cb').is(':checked') ? 1 : 0 1096 }).then(resp => { 1097 finish(); 1098 if (resp.success) window.location.href = resp.data.redirect_url; 1099 else { 1100 const errorMsg = (resp.data && resp.data.message) ? resp.data.message : (resp.data || (typeof resp === 'string' ? resp : 'Unknown error')); 1101 setMessage($msgBox, errorMsg, false); 1102 } 1103 }).catch(err => { 1104 finish(); 1105 setMessage($msgBox, err.message || 'Error occurred.', false); 1106 }); 1107 }); 1108 }); 1109 1110 // Password Toggle (instance-scoped) 1111 $(document).on('click', '.js-wawp-password-toggle', function () { 1112 const $form = $(this).closest('.wawp-merged-form'); 1113 const input = $form.find('.js-wawp-password-input'); 1114 const type = input.attr('type') === 'password' ? 'text' : 'password'; 1115 input.attr('type', type); 1116 $(this).toggleClass('ri-eye-line ri-eye-off-line'); 1117 }); 1118 1119 // Check cooldowns on load
1120 checkCooldown('phone'); 1121 checkCooldown('email'); 1122 1123 /* ------------------------------------------------------------ 1124 * Enter Key Support 1125 * ---------------------------------------------------------- */ 1126 function handleEnter(inputSel, btnSel) { 1127 $(document).on('keypress', inputSel, function (e) { 1128 if (e.which === 13) { 1129 e.preventDefault(); 1130 $(btnSel).first().trigger('click'); 1131 } 1132 }); 1133 } 1134 1135 // Phone Input -> Submit first visible request button (instance-scoped) 1136 $(document).on('keypress', '.wawp-phone-form [name="phone"]', function (e) { 1137 if (e.which === 13) { 1138 e.preventDefault(); 1139 $(this).blur(); 1140 $(this).closest('form').find('.wawp-request-btn:visible').first().trigger('click'); 1141 } 1142 }); 1143 1144 // Phone OTP split inputs -> Verify on Enter 1145 $(document).on('keypress', '.wawp-otp-group-phone .wawp-otp-inputs input', function (e) { 1146 if (e.which === 13) { 1147 e.preventDefault(); 1148 $(this).closest('form').find('.wawp-verify-otp-phone:visible').first().trigger('click'); 1149 } 1150 }); 1151 1152 // Email Input -> Check context (Password login or OTP request) (instance-scoped) 1153 $(document).on('keypress', '.js-wawp-email-input', function (e) { 1154 if (e.which === 13) { 1155 e.preventDefault(); 1156 $(this).blur(); 1157 const $form = $(this).closest('.wawp-merged-form'); 1158 const $passBtn = $form.find('.js-wawp-login-submit-password:visible'); 1159 if ($passBtn.length) { 1160 $passBtn.trigger('click'); 1161 } else { 1162 $form.find('.js-wawp-request-otp-email:visible').trigger('click'); 1163 } 1164 } 1165 }); 1166 1167 // Password -> Login (instance-scoped) 1168 $(document).on('keypress', '.js-wawp-password-input', function (e) { 1169 if (e.which === 13) { 1170 e.preventDefault(); 1171 $(this).closest('form').find('.js-wawp-login-submit-password:visible').trigger('click'); 1172 } 1173 }); 1174 1175 // Email OTP split inputs -> Verify on Enter (instance-scoped) 1176 $(document).on('keypress', '.js-wawp-otp-group-email .wawp-otp-inputs input', function (e) { 1177 if (e.which === 13) { 1178 e.preventDefault(); 1179 $(this).closest('form').find('.js-wawp-verify-otp-email:visible').first().trigger('click'); 1180 } 1181 }); 1182 1183 // Fast Login Toggles (instance-scoped) - Re-init recaptcha just in case 1184 $(document).on('click', '.wawp-fast-login-signup-toggle, .wawp-fast-login-login-toggle', function () { 1185 setTimeout(function () { 1186 $(document).trigger('wawp_recaptcha_init'); 1187 }, 100); 1188 }); 1189 1190 /* ------------------------------------------------------------ 1191 * Legacy Client Fixes (Adapted for modern classes) 1192 * ---------------------------------------------------------- */ 1193 // 1. Force enable buttons stuck in disabled state (e.g. from bfcache or browser bugs) 1194 $(document).on('mouseenter', '.wawp-btn, .wawp-request-btn', function () { 1195 var $btn = $(this); 1196 // Do not force enable if explicitly in a loading or active cooldown state 1197 if ($btn.hasClass('is-loading') || $btn.closest('.wawp-action-buttons').find('.wawp-resend-timer').length > 0) { 1198 return; 1199 } 1200 $btn.css('pointer-events', 'auto'); 1201 $btn.removeAttr('disabled'); 1202 $btn.removeClass('disabled loading blockUI'); 1203 }); 1204 1205 // 2. Formatting Sync: Force focus to the request button 1 second after typing 1206 var typingTimer; 1207 var doneTypingInterval = 1000; 1208 $(document).on('keyup', '.wawp-phone-form [name="phone"]', function () { 1209 const $inp = $(this); 1210 clearTimeout(typingTimer); 1211 if ($inp.val()) { 1212 typingTimer = setTimeout(function () { 1213 var valLength = $inp.val().replace(/\D/g, '').length; 1214 if (valLength >= 7) { 1215 var $whatsappBtn = $inp.closest('form').find('.wawp-request-btn[data-method="whatsapp"]:visible, .wawp-request-btn:visible').first(); 1216 if ($whatsappBtn.length) { $whatsappBtn.focus(); } 1217 } 1218 }, doneTypingInterval); 1219 } 1220 }); 1221 $(document).on('keydown', '.wawp-phone-form [name="phone"]', function () { 1222 clearTimeout(typingTimer); 1223 }); 1224 1225 // 3. Modern Split OTP Input Logic 1226 $(document).on('input', '.wawp-otp-inputs input', function () { 1227 const $this = $(this);
1228 const $inputs = $this.parent().find('input'); 1229 const targetId = $this.parent().data('target'); 1230 const $hiddenInput = $(targetId); 1231 1232 // Move forward if filled 1233 if ($this.val().length === 1) { 1234 $this.addClass('filled'); 1235 const nextIdx = $inputs.index($this) + 1; 1236 if (nextIdx < $inputs.length) { 1237 $inputs.eq(nextIdx).focus(); 1238 } 1239 } else { 1240 $this.removeClass('filled'); 1241 } 1242 1243 // Update hidden input 1244 let fullValue = ''; 1245 $inputs.each(function () { 1246 fullValue += $(this).val(); 1247 }); 1248 $hiddenInput.val(fullValue); 1249 }); 1250 1251 $(document).on('keydown', '.wawp-otp-inputs input', function (e) { 1252 const $this = $(this); 1253 const $inputs = $this.parent().find('input'); 1254 1255 // Move backward on backspace if empty 1256 if (e.key === 'Backspace' && $this.val().length === 0) { 1257 const prevIdx = $inputs.index($this) - 1; 1258 if (prevIdx >= 0) { 1259 $inputs.eq(prevIdx).focus(); 1260 } 1261 } 1262 }); 1263 1264 // Handle Paste for Split OTP 1265 $(document).on('paste', '.wawp-otp-inputs input', function (e) { 1266 e.preventDefault(); 1267 const $this = $(this); 1268 const $inputs = $this.parent().find('input'); 1269 const targetId = $this.parent().data('target'); 1270 const $hiddenInput = $(targetId); 1271 1272 const pasteData = (e.originalEvent || e).clipboardData.getData('text').slice(0, 6).split(''); 1273 1274 pasteData.forEach((char, i) => { 1275 if ($inputs.eq(i).length) { 1276 $inputs.eq(i).val(char).addClass('filled'); 1277 } 1278 }); 1279 1280 $hiddenInput.val(pasteData.join('')); 1281 if ($inputs.eq(pasteData.length - 1).length) { 1282 $inputs.eq(pasteData.length - 1).focus(); 1283 } else { 1284 $inputs.last().focus(); 1285 } 1286 }); 1287 1288 $(document).on('click', '.js-wawp-back-to-input', function (e) { 1289 e.preventDefault(); 1290 if (pollInterval) clearInterval(pollInterval); 1291 const $form = $(this).closest('form'); 1292 const $wrapper = $(this).closest('.wawp-otp-login-form'); 1293 $form.find('.wawp-list-waiting-container').remove(); 1294 $wrapper.find('.wawp-tabs').show(); 1295 $form.find('.wawp-remember-me').removeClass('wawp-hide-force'); 1296 $wrapper.find('.wawp-login-branding').show(); 1297 if ($form.hasClass('js-wawp-email-form')) { 1298 $form.find('.js-wawp-email-group, .js-wawp-request-otp-email, .js-wawp-show-password-btn, .wawp-switch-form').show(); 1299 } else { 1300 $form.find('.wawp-phone-group, .wawp-action-buttons, .wawp-separator, .wawp-switch-form').removeClass('wawp-hide-force'); 1301 $form.find('.wawp-login-message-phone').empty(); 1302 } 1303 if (typeof firebase !== 'undefined' && firebase.auth) { 1304 resetFirebaseVerifier(); 1305 } 1306 }); 1307 1308 let pollInterval = null; 1309 function startListPolling(phone, authId, $msgBox) { 1310 if (pollInterval) clearInterval(pollInterval); 1311 1312 let attempts = 0; 1313 const maxAttempts = 60; // 3 minutes (3s * 60) 1314 const isEmail = $msgBox.hasClass('js-wawp-msg-box-email'); 1315 const $form = $msgBox.closest('form'); 1316 1317 pollInterval = setInterval(() => { 1318 attempts++; 1319 if (attempts > maxAttempts) { 1320 clearInterval(pollInterval); 1321 const $wrapperP = $form.closest('.wawp-otp-login-form'); 1322 $form.find('.wawp-list-waiting-container').remove(); 1323 $wrapperP.find('.wawp-tabs').show(); 1324 $form.find('.wawp-remember-me').removeClass('wawp-hide-force'); 1325 $wrapperP.find('.wawp-login-branding').show(); 1326 setMessage($msgBox, 'Authentication timeout. Please try again.', false); 1327 if (isEmail) { 1328 $form.find('.js-wawp-email-group, .js-wawp-request-otp-email, .js-wawp-show-password-btn, .wawp-switch-form').show(); 1329 } else { 1330 $form.find('.wawp-phone-group, .wawp-action-buttons, .wawp-separator, .wawp-switch-form').removeClass('wawp-hide-force'); 1331 } 1332 return; 1333 } 1334 1335 wawpRest('status', { 1336 otp_transient: authId, 1337 whatsapp: phone, 1338 remember: isEmail ? ($form.find('.wawp-remember-email-cb').is(':checked') ? 1 : 0) : ($form.find('.wawp-remember-phone-cb').is(':checked') ? 1 : 0) 1339 }).then(resp => { 1340 if (resp.success && resp.data.verified) { 1341 clearInterval(pollInterval); 1342 $('.wawp-list-waiting-container').html('<div class="wawp-list-waiting-icon" style="background:#15803d"><i class="ri-check-line"></i></div><h4 class="wawp-list-waiting-title">' + (window.wawpOtpL10n.verified || 'Verified!') + '</h4><p class="wawp-list-waiting-text">' + (window.wawpOtpL10n.redirectingDashboard || 'Redirecting you to dashboard...') + '</p>'); 1343 1344 setTimeout(() => { 1345 if (resp.data.redirect_url) { 1346 window.location.href = resp.data.redirect_url; 1347 } else { 1348 window.location.reload(); 1349 } 1350 }, 1000); 1351 } else if (resp.success && resp.data.status === 'rejected') { 1352 clearInterval(pollInterval); 1353 const $wrapperR = $form.closest('.wawp-otp-login-form'); 1354 $form.find('.wawp-list-waiting-container').remove(); 1355 $wrapperR.find('.wawp-tabs').show(); 1356 $form.find('.wawp-remember-me').removeClass('wawp-hide-force'); 1357 $wrapperR.find('.wawp-login-branding').show(); 1358 setMessage($msgBox, isEmail ? (window.wawpOtpL10n.loginRejectedEmail || 'Login request was rejected.') : (window.wawpOtpL10n.loginRejectedPhone || 'Login request was rejected from your phone.'), false); 1359 if (isEmail) { 1360 $form.find('.js-wawp-email-group, .js-wawp-request-otp-email, .js-wawp-show-password-btn, .wawp-switch-form').show(); 1361 } else { 1362 $form.find('.wawp-phone-group, .wawp-action-buttons, .wawp-separator, .wawp-switch-form').removeClass('wawp-hide-force'); 1363 } 1364 } 1365 }).catch(() => { 1366 // Silently ignore polling errors to keep trying 1367 }); 1368 }, parseInt(wawpOtpAjax.polling_interval) || 1500); 1369 } 1370});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.