1(function () { 2 if (!window.PasskeyAuthLogin) { 3 return; 4 } 5 6 var EMAIL_STORAGE_KEY = 'passkeyauth_client_login_email'; 7 var PASSKEY_DEVICE_STORAGE_KEY = 'passkeyauth_client_device_passkeys'; 8 var cfg = window.PasskeyAuthLogin; 9 var hasWebAuthn = !!(window.PublicKeyCredential); 10 var passkeyUiEnabled = hasWebAuthn && cfg.passkeyEnabled !== false && cfg.authMode !== 'password_only'; 11 var availabilityTimers = typeof WeakMap !== 'undefined' ? new WeakMap() : null; 12 13 function isPasskeyOnlyMode() { 14 return !!(cfg && cfg.authMode === 'passkey_only'); 15 } 16 17 function allowsUsernameless() { 18 if (!cfg) { 19 return false; 20 } 21 if (cfg.allowUsernameless === true || cfg.allowUsernameless === 1 || cfg.allowUsernameless === '1') { 22 return true; 23 } 24 return cfg.residentKey === 'required'; 25 } 26 27 function b64urlToBuffer(base64url) { 28 var padding = '='.repeat((4 - (base64url.length % 4)) % 4); 29 var base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/'); 30 var raw = atob(base64); 31 var out = new Uint8Array(raw.length); 32 for (var i = 0; i < raw.length; i++) { 33 out[i] = raw.charCodeAt(i); 34 } 35 36 return out.buffer; 37 } 38 39 function bufferToB64url(buffer) { 40 var bytes = new Uint8Array(buffer); 41 var str = ''; 42 for (var i = 0; i < bytes.byteLength; i++) { 43 str += String.fromCharCode(bytes[i]); 44 } 45 46 return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); 47 } 48 49 function showLoginError(message) { 50 var text = message || (cfg && cfg.errorMessage) || 'Passkey sign-in failed.'; 51 window.alert(text); 52 } 53 54 function showLoginFlash(form) { 55 if (!cfg || !cfg.flash || !cfg.flash.message) { 56 return; 57 } 58 var existing = document.getElementById('passkeyauth-login-flash'); 59 if (existing && existing.parentNode) { 60 existing.parentNode.removeChild(existing); 61 } 62 63 var type = String(cfg.flash.type || 'warning').toLowerCase(); 64 if (type === 'danger' || type === 'error') { 65 type = 'danger'; 66 } else if (type === 'success') { 67 type = 'success'; 68 } else if (type === 'info') { 69 type = 'info'; 70 } else { 71 type = 'warning'; 72 } 73 74 var alert = document.createElement('div'); 75 alert.id = 'passkeyauth-login-flash'; 76 alert.className = 'alert alert-' + type + ' text-center'; 77 alert.setAttribute('role', 'alert'); 78 alert.style.cssText = 'margin: 0 0 16px 0; width: 100%; box-sizing: border-box;'; 79 alert.textContent = String(cfg.flash.message); 80 81 // 1. Checkout existing user signin container 82 var existingBox = document.getElementById('containerExistingUserSignin'); 83 if (existingBox) { 84 existingBox.insertBefore(alert, existingBox.firstChild); 85 finishFlash(alert); 86 return; 87 } 88 89 // 2. Insert inside the login card directly before the login form 90 var passwordInput = document.querySelector( 91 'form input[type="password"], form input[name="password"], form input[name="loginpw"]' 92 ); 93 var loginForm = form 94 || (passwordInput && passwordInput.form) 95 || document.querySelector('form#frmLogin, form[action*="dologin"], form[action*="login"]'); 96 97 if (loginForm && loginForm.parentNode) { 98 loginForm.parentNode.insertBefore(alert, loginForm); 99 } else if (loginForm) { 100 loginForm.insertBefore(alert, loginForm.firstChild); 101 } else { 102 var loginContainer = document.querySelector('.login-container .card-body, .login-container, .logincontainer, #main-body .card-body, #main-body .card'); 103 if (loginContainer) { 104 loginContainer.insertBefore(alert, loginContainer.firstChild); 105 } else { 106 var main = document.querySelector('#main-body, .main-content, main'); 107 if (main) { 108 main.insertBefore(alert, main.firstChild); 109 } else { 110 document.body.appendChild(alert); 111 } 112 } 113 } 114 115 finishFlash(alert); 116 } 117 118 function finishFlash(alert) { 119 if (alert && alert.scrollIntoView) { 120 try { 121 alert.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); 122 } catch (err) { 123 // Ignore scroll failures. 124 } 125 } 126 127 // Show once per page load. 128 cfg.flash = null; 129 } 130 131 function getFormCsrfToken(form) { 132 if (form) { 133 var input = form.querySelector('input[name="token"], input[name="csrfToken"], input[name="csrf_token"]'); 134 if (input && input.value) { 135 return input.value; 136 } 137 } 138 var docInput = document.querySelector('input[name="token"], input[name="csrfToken"], input[name="csrf_token"]'); 139 if (docInput && docInput.value) { 140 return docInput.value; 141 } 142 return (cfg && cfg.csrfToken) || ''; 143 } 144 145 function postJson(action, payload) { 146 var endpoint = cfg.endpoint || ''; 147 var url = endpoint + (endpoint.indexOf('?') >= 0 ? '&' : '?') + 'action=' + encodeURIComponent(action); 148 payload = payload || {}; 149 var token = getFormCsrfToken() || cfg.csrfToken || payload.token || payload.csrfToken || ''; 150 if (token) { 151 payload.token = token; 152 payload.csrfToken = token; 153 } 154 155 return fetch(url, { 156 method: 'POST', 157 credentials: 'same-origin', 158 headers: { 'Content-Type': 'application/json' }, 159 body: JSON.stringify(payload) 160 }).then(function (res) { 161 return res.text().then(function (text) { 162 var data = null; 163 try { 164 data = text ? JSON.parse(text) : null; 165 } catch (e) { 166 throw new Error('Passkey login failed: invali
166d server response.'); 167 } 168 if (!data || typeof data !== 'object') { 169 throw new Error('Passkey login failed: empty server response.'); 170 } 171 if (!res.ok && !data.message) { 172 data.message = cfg.errorMessage || 'Passkey sign-in failed.'; 173 data.ok = false; 174 } 175 if (data.token || data.csrfToken) { 176 cfg.csrfToken = data.token || data.csrfToken; 177 } 178 return data; 179 }); 180 }); 181 } 182 183 function findEmailInput(form) { 184 if (!form) { 185 return null; 186 } 187 var scope = checkoutFieldScope(form); 188 var preferred = scope.querySelector( 189 'input[name="loginemail"], input#inputLoginEmail, input#loginemail, input[name="loginusername"]' 190 ); 191 if (preferred) { 192 return preferred; 193 } 194 var input = scope.querySelector( 195 'input[type="email"], input[name="username"], input[name="loginemail"], input[name="email"], input[name="loginusername"], input[name="login"], input#inputEmail, input#username, input#loginemail' 196 ); 197 if (input) { 198 return input; 199 } 200 var textInputs = scope.querySelectorAll('input[type="text"], input:not([type])'); 201 for (var i = 0; i < textInputs.length; i++) { 202 var el = textInputs[i]; 203 var id = String(el.id || '').toLowerCase(); 204 var name = String(el.getAttribute('name') || '').toLowerCase(); 205 if (name.indexOf('email') !== -1 || name.indexOf('user') !== -1 || name.indexOf('login') !== -1 || id.indexOf('email') !== -1 || id.indexOf('user') !== -1) { 206 return el; 207 } 208 } 209 return textInputs.length > 0 ? textInputs[0] : null; 210 } 211 212 function findPasswordInput(form) { 213 if (!form) { 214 return null; 215 } 216 var scope = checkoutFieldScope(form); 217 var preferred = scope.querySelector('input[name="loginpw"], input#inputLoginPassword, input#loginpw'); 218 if (preferred) { 219 return preferred; 220 } 221 return scope.querySelector('input[type="password"], input[name="password"]'); 222 } 223 224 function findNativeSubmit(form) { 225 if (!form) { 226 return null; 227 } 228 229 var existingLogin = form.querySelector('#btnExistingLogin') 230 || document.getElementById('btnExistingLogin'); 231 if (existingLogin && !isCompleteOrderControl(existingLogin)) { 232 return existingLogin; 233 } 234 235 var candidates = form.querySelectorAll( 236 'button[type="submit"], input[type="submit"], button#btnLogin, input#btnLogin, button#btnExistingLogin, button[name="login"], input[name="login"], button.btn, input[type="button"]' 237 ); 238 var fallback = null; 239 240 for (var i = 0; i < candidates.length; i++) { 241 var el = candidates[i]; 242 if (isCompleteOrderControl(el)) { 243 continue; 244 } 245 if (el.classList && ( 246 el.classList.contains('passkeyauth-signin-btn') 247 || el.classList.contains('passkeyauth-mode-btn') 248 )) { 249 continue; 250 } 251 252 var id = String(el.id || '').toLowerCase(); 253 var name = String(el.getAttribute('name') || '').toLowerCase(); 254 var text = String((el.value || '') + ' ' + (el.textContent || '')).replace(/\s+/g, ' ').trim().toLowerCase(); 255 256 if ( 257 id === 'btnlogin' 258 || id === 'btnexistinglogin' 259 || id.indexOf('btnlogin') !== -1 260 || id === 'login' 261 || name === 'login' 262 || name === 'loginsubmit' 263 || text === 'login' 264 || text === 'log in' 265 || text === 'sign in' 266 || text.indexOf('login') === 0 267 ) { 268 return el; 269 } 270 271 if (!fallback && (el.matches('button[type="submit"], input[type="submit"], button.btn-primary, button.btn'))) { 272 fallback = el; 273 } 274 } 275 276 return fallback || form.querySelector('button[type="submit"], input[type="submit"], button'); 277 } 278 279 function isCompleteOrderControl(el) { 280 if (!el) { 281 return false; 282 } 283 284 var id = String(el.id || '').toLowerCase(); 285 var name = String(el.getAttribute('name') || '').toLowerCase(); 286 var cls = String(el.className || '').toLowerCase(); 287 var text = String((el.value || '') + ' ' + (el.textContent || '')).replace(/\s+/g, ' ').trim().toLowerCase(); 288 289 if ( 290 id.indexOf('completeorder') !== -1 291 || id.indexOf('btncomplete') !== -1 292 || name.indexOf('completeorder') !== -1 293 || cls.indexOf('btn-checkout') !== -1 294 || cls.indexOf('complete-order') !== -1 295 ) { 296 return true; 297 } 298 299 if (text.indexOf('complete order') !== -1 || text.indexOf('place order') !== -1) { 300 return true; 301 } 302 303 return false; 304 } 305 306 function getCheckoutSsoRedirectPath() { 307 var path = String(window.location.pathname || ''); 308 var search = String(window.location.search || ''); 309 var file = path.split('/').pop() || ''; 310 311 if (file.toLowerCase().indexOf('cart.php') !== -1 || search.toLowerCase().indexOf('a=checkout') !== -1) { 312 return 'cart.php?a=checkout'; 313 } 314 315 if (!file) { 316 return ''; 317 } 318 319 return file + search; 320 } 321 322 function hasCheckoutExistingLogin(form) { 323 if (!form) { 324 return false; 325 } 326 if (String(form.id || '').toLowerCase() === 'frmcheckout') { 327 return true; 328 } 329 return !!form.querySelector( 330 '#containerExistingUserSignin, input[name="loginemail"], input[name="loginpw"], #btnExistingLogin, #existingLoginButton' 331 ); 332 } 333 334 function checkoutFieldScope(form) { 335 if (!form) { 336 return form; 337 } 338 if (!hasCheckoutExistingLogin(form) && !isCheckoutLoginForm(form)) { 339 return form; 340 } 341 var box = form.querySelector('#containerExistingUserSignin') 342 || document.getElementById('containerExistingUserSignin'); 343 if (box && form.contains(box)) { 344 return box; 345 } 346 return form; 347 } 348 349 function isRegistrationForm(form) { 350 if (!form) { 351 return false; 352 } 353 354 // cart.php checkout form has both existing-customer login and new-customer fields. 355 if (hasCheckoutExistingLogin(form) || isCheckoutLoginForm(form)) { 356 return false; 357 } 358 359 var path = (window.location.pathname || '').toLowerCase(); 360 var search = (window.location.search || '').toLowerCase() + (window.location.hash || '').toLowerCase(); 361 var href = (window.location.href || '').toLowerCase(); 362 363 if ( 364 path.indexOf('register') !== -1 365 || search.indexOf('register') !== -1 366 || href.indexOf('register.php') !== -1 367 || search.indexOf('rp=/register') !== -1 368 || search.indexOf('rp=%2fregister') !== -1 369 ) { 370 return true; 371 } 372 373 var formId = String(form.id || '').toLowerCase(); 374 if (formId.indexOf('register') !== -1 || formId.indexOf('frmregister') !== -1) { 375 return true; 376 } 377 378 var formClass = String(form.className || '').toLowerCase(); 379 if (formClass.indexOf('register') !== -1) { 380 return true; 381 } 382 383 if (form.closest && form.closest('#registration, .register-form, .page-register, #frmRegister, #clientRegistrationForm, .register')) { 384 return true; 385 } 386 387 var action = String(form.getAttribute('action') || '').toLowerCase(); 388 if ( 389 action.indexOf('register.php') !== -1 390 || action.indexOf('rp=/register') !== -1 391 || action.indexOf('rp=%2fregister') !== -1 392 ) { 393 return true; 394 } 395 396 if (form.querySelector( 397 'input[name="firstname"], input[name="lastname"], input[name="accepttos"], input[name="customfield"], input[name="clientareaaction"][value="register"]' 398 )) { 399 return true; 400 } 401 402 return false; 403 } 404 405 function isStandaloneLoginForm(form) { 406 if (!form) { 407 return false; 408 } 409 if (form.classList.contains('login-form')) { 410 return true; 411 } 412 if (form.closest('.logincontainer, .login-form, #login, .page-login')) { 413 return true; 414 } 415 var path = (window.location.pathname || '').toLowerCase(); 416 var search = (window.location.search || '').toLowerCase() + (window.location.hash || '').toLowerCase(); 417 if (path.indexOf('login') !== -1 || search.indexOf('rp=/login') !== -1 || search.indexOf('rp=%2flogin') !== -1) { 418 return true; 419 } 420 return false; 421 } 422 423 function isCheckoutLoginForm(form) { 424 if (!form || isStandaloneLoginForm(form)) { 425 return false; 426 } 427 428 var path = (window.location.pathname || '').toLowerCase(); 429 var search = (window.location.search || '').toLowerCase(); 430 if ( 431 path.indexOf('cart.php') !== -1 432 || path.indexOf('/cart/') !== -1 433 || path.slice(-5) === '/cart' 434 || search.indexOf('a=checkout') !== -1 435 || search.indexOf('rp=/cart') !== -1 436 || search.indexOf('rp=%2fcart') !== -1 437 ) { 438 return true; 439 } 440 441 if (form.closest( 442 '#frmCheckout, #containerExistingUserSignin, .already-registered, .secondary-cart-body, #checkout, .wgs-checkout, .cart-body' 443 )) { 444 return true; 445 } 446 447 var action = (form.getAttribute('action') || '').toLowerCase(); 448 if (action.indexOf('cart.php') !== -1) { 449 return true; 450 } 451 452 return false; 453 } 454 455 function passwordGroup(form) { 456 var password = findPasswordInput(form); 457 if (!password) { 458 return null; 459 } 460 461 var group = password.closest('.form-group') || password.parentElement; 462 if (!group || group === form) { 463 return null; 464 } 465 466 // wrapper also has the email field â leave it. 467 if (group.querySelector('input[type="email"], input[name="username"], input[name="loginemail"], input[name="email"]')) { 468 return null; 469 } 470 471 return group; 472 } 473 474 function wrapNativeSubmit(form) { 475 var submit = findNativeSubmit(form); 476 if (!submit || isCompleteOrderControl(submit)) { 477 return null; 478 } 479 480 // Complete Order on checkout is off-limits. 481 submit.classList.remove('passkeyauth-native-submit'); 482 if (submit.closest('#btnCompleteOrder, .btn-checkout, [id*="CompleteOrder"]')) { 483 return null; 484 } 485 486 submit.classList.add('passkeyauth-native-submit'); 487 488 var wrap = submit.closest('.passkeyauth-native-submit-wrap'); 489 if (wrap) { 490 return wrap; 491 } 492 493 var parent = submit.parentElement; 494 if (parent && parent !== form) { 495 // parent also has Complete Order 496 if (parent.querySelector && ( 497 parent.querySelector('#btnCompleteOrder, [id*="CompleteOrder"]') 498 || isCompleteOrderControl(parent.querySelector('button[type="submit"], input[type="submit"]')) 499 )) { 500 // wrap the Login control only 501 wrap = document.createElement('div'); 502 wrap.className = 'passkeyauth-native-submit-wrap'; 503 if (submit.parentNode) { 504 submit.parentNode.insertBefore(wrap, submit); 505 wrap.appendChild(submit); 506 } 507 return wrap; 508 } 509 510 var onlySubmitChild = true; 511 var children = parent.children; 512 for (var i = 0; i < children.length; i++) { 513 var child = children[i]; 514 if (child === submit) { 515 continue; 516 } 517 if (child.matches && child.matches('input[type="hidden"]')) { 518 continue; 519 } 520 onlySubmitChild = false; 521 break; 522 } 523 if (onlySubmitChild) { 524 parent.classList.add('passkeyauth-native-submit-wrap'); 525 return parent; 526 } 527 } 528 529 wrap = document.createElement('div'); 530 wrap.className = 'passkeyauth-native-submit-wrap'; 531 if (submit.parentNode) { 532 submit.parentNode.insertBefore(wrap, submit); 533 wrap.appendChild(submit); 534 } 535 return wrap; 536 } 537 538 function normalizeCheckoutLayout(form) { 539 if (!form || form.dataset.passkeyCheckoutNormalized === '1') { 540 return; 541 } 542 form.dataset.passkeyCheckoutNormalized = '1'; 543 form.classList.add('passkeyauth-checkout-form'); 544 545 var scope = checkoutFieldScope(form) || form; 546 var rows = scope.querySelectorAll('.row'); 547 for (var r = 0; r < rows.length; r++) { 548 var row = rows[r]; 549 var cols = []; 550 try { 551 cols = row.querySelectorAll(':scope > [class*="col-"]'); 552 } catch (e) { 553 cols = []; 554 } 555 if (!cols.length) { 556 cols = []; 557 for (var c = 0; c < row.children.length; c++) { 558 var child = row.children[c]; 559 if (child.className && String(child.className).indexOf('col-') !== -1) { 560 cols.push(child); 561 } 562 } 563 } 564 var touchesLoginFields = false; 565 for (var i = 0; i < cols.length; i++) { 566 if (cols[i].querySelector( 567 'input[type="email"], input[name="username"], input[name="loginemail"], input[name="email"], input[type="password"], input[name="password"]' 568 )) { 569 touchesLoginFields = true; 570 break; 571 } 572 } 573 if (!touchesLoginFields) { 574 continue; 575 } 576 row.classList.add('passkeyauth-checkout-stack-row'); 577 for (var j = 0; j < cols.length; j++) { 578 cols[j].classList.add('passkeyauth-checkout-stack-col'); 579 } 580 } 581 582 var emailInput = findEmailInput(form); 583 if (emailInput) { 584 var emailGroup = emailInput.closest('.form-group') || emailInput.parentElement; 585 if (emailGroup && emailGroup !== form) { 586 emailGroup.classList.add('passkeyauth-checkout-email-group'); 587 } 588 } 589 590 wrapNativeSubmit(form); 591 } 592 593 function reorderCheckoutFields(form) { 594 if (!form || !form.classList.contains('passkeyauth-checkout-form')) { 595 return; 596 } 597 598 var emailInput = findEmailInput(form); 599 var emailGroup = emailInput 600 ? (emailInput.closest('.passkeyauth-checkout-email-group, .form-group') || emailInput.parentElement) 601 : null; 602 var choiceWrap = form.querySelector('.passkeyauth-auth-method-wrap'); 603 var passwordWrap = form.querySelector('.passkeyauth-password-group') || passwordGroup(form); 604 var passkeySection = form.querySelector('.passkeyauth-passkey-section'); 605 var submitWrap = form.querySelector('.passkeyauth-native-submit-wrap') || wrapNativeSubmit(form); 606 var rememberInput = form.querySelector( 607 'input[name="rememberme"], input[name="remember"], input#rememberme, input#inputRememberMe' 608 ); 609 var rememberGroup = nu
609ll; 610 if (rememberInput) { 611 rememberGroup = rememberInput.closest('.form-check, .checkbox, .custom-control, .form-group, label') 612 || rememberInput.parentElement; 613 if (rememberGroup === form || (submitWrap && rememberGroup === submitWrap)) { 614 rememberGroup = rememberInput.parentElement; 615 } 616 if (rememberGroup === form) { 617 rememberGroup = null; 618 } 619 } 620 621 var sequence = []; 622 if (emailGroup) { 623 sequence.push(emailGroup); 624 } 625 if (choiceWrap) { 626 sequence.push(choiceWrap); 627 } 628 if (passwordWrap) { 629 sequence.push(passwordWrap); 630 } 631 if (passkeySection) { 632 sequence.push(passkeySection); 633 } 634 if (rememberGroup && sequence.indexOf(rememberGroup) === -1) { 635 sequence.push(rememberGroup); 636 } 637 if (submitWrap) { 638 sequence.push(submitWrap); 639 } 640 if (sequence.length < 2) { 641 return; 642 } 643 644 var stack = form.querySelector('.passkeyauth-checkout-fields'); 645 if (!stack) { 646 stack = document.createElement('div'); 647 stack.className = 'passkeyauth-checkout-fields'; 648 649 var stackRow = form.querySelector('.passkeyauth-checkout-stack-row'); 650 if (stackRow && stackRow.parentNode) { 651 stackRow.parentNode.insertBefore(stack, stackRow); 652 } else if (emailGroup && emailGroup.parentNode) { 653 emailGroup.parentNode.insertBefore(stack, emailGroup); 654 } else { 655 form.insertBefore(stack, form.firstChild); 656 } 657 } 658 659 for (var i = 0; i < sequence.length; i++) { 660 stack.appendChild(sequence[i]); 661 } 662 663 // leftover empty col/row after we moved the button 664 var rows = form.querySelectorAll('.passkeyauth-checkout-stack-row'); 665 for (var r = 0; r < rows.length; r++) { 666 var row = rows[r]; 667 if (!row.querySelector('input, button, select, textarea, .passkeyauth-auth-method-wrap, .passkeyauth-passkey-section')) { 668 row.classList.add('passkeyauth-checkout-row-empty'); 669 } 670 } 671 } 672 673 function rememberEmail(value) { 674 try { 675 if (value) { 676 window.localStorage.setItem(EMAIL_STORAGE_KEY, value); 677 } 678 } catch (e) { 679 // private mode / blocked storage 680 } 681 } 682 683 function restoreRememberedEmail(form) { 684 var emailInput = findEmailInput(form); 685 if (!emailInput || emailInput.value) { 686 return; 687 } 688 689 try { 690 var saved = window.localStorage.getItem(EMAIL_STORAGE_KEY); 691 if (saved) { 692 emailInput.value = saved; 693 } 694 } catch (e) { 695 // Ignore storage failures. 696 } 697 } 698 699 function updateToggleState(form, mode) { 700 var choice = form.querySelector('.passkeyauth-login-choice'); 701 if (!choice) { 702 return; 703 } 704 705 var passwordBtn = choice.querySelector('.passkeyauth-mode-password'); 706 var passkeyBtn = choice.querySelector('.passkeyauth-mode-passkey'); 707 var isPasskey = mode === 'passkey'; 708 709 if (passwordBtn) { 710 passwordBtn.classList.toggle('is-active', !isPasskey); 711 passwordBtn.setAttribute('aria-pressed', isPasskey ? 'false' : 'true'); 712 } 713 if (passkeyBtn) { 714 passkeyBtn.classList.toggle('is-active', isPasskey); 715 passkeyBtn.setAttribute('aria-pressed', isPasskey ? 'true' : 'false'); 716 } 717 718 refreshModeButtonStyles(form); 719 } 720 721 function normalizeStyleName(name) { 722 name = name || 'blue-gradient'; 723 return name === 'gradient' ? 'blue-gradient' : name; 724 } 725 726 function clearButtonStyle(btn) { 727 if (!btn) { 728 return; 729 } 730 731 var classes = (btn.className || '').split(/\s+/); 732 var filtered = []; 733 for (var i = 0; i < classes.length; i++) { 734 var className = classes[i]; 735 if (className && className.indexOf('passkeyauth-btn-style-') !== 0) { 736 filtered.push(className); 737 } 738 } 739 btn.className = filtered.join(' '); 740 btn.style.removeProperty('background-color'); 741 btn.style.removeProperty('background-image'); 742 btn.style.removeProperty('color'); 743 btn.style.removeProperty('border-color'); 744 btn.style.removeProperty('box-shadow'); 745 } 746 747 function applyModeButtonStyle(btn, styleName, customColors, isActive) { 748 if (!btn) { 749 return; 750 } 751 752 clearButtonStyle(btn); 753 if (!isActive) { 754 return; 755 } 756 757 styleName = normalizeStyleName(styleName); 758 btn.classList.add('passkeyauth-btn-style-' + styleName); 759 var isPassword = btn.classList.contains('passkeyauth-mode-password'); 760 var scopeClass = isPassword 761 ? (cfg.passwordModeScopeClass || 'passkeyauth-btn-custom-client-password') 762 : (cfg.passkeyModeScopeClass || 'passkeyauth-btn-custom-client-passkey'); 763 if (scopeClass) { 764 btn.classList.add(scopeClass); 765 } 766 var extraClass = isPassword ? cfg.passwordModeCustomClass : cfg.passkeyModeCustomClass;
767 String(extraClass || '').split(/\s+/).forEach(function (c) { 768 if (/^[A-Za-z_][A-Za-z0-9_-]{0,63}$/.test(c)) { 769 btn.classList.add(c); 770 } 771 }); 772 if (styleName === 'custom' && customColors) { 773 btn.style.setProperty('background-color', customColors.bg || '#2563eb', 'important'); 774 btn.style.setProperty('background-image', 'none', 'important'); 775 btn.style.setProperty('color', customColors.text || '#ffffff', 'important'); 776 btn.style.setProperty('border-color', customColors.border || '#2563eb', 'important'); 777 } 778 } 779 780 function refreshModeButtonStyles(form) { 781 var choice = form.querySelector('.passkeyauth-login-choice'); 782 if (!choice) { 783 return; 784 } 785 786 var passwordBtn = choice.querySelector('.passkeyauth-mode-password'); 787 var passkeyBtn = choice.querySelector('.passkeyauth-mode-passkey'); 788 applyModeButtonStyle( 789 passwordBtn, 790 cfg.passwordModeBtnStyle, 791 cfg.passwordModeCustomColors, 792 !!(passwordBtn && passwordBtn.classList.contains('is-active')) 793 ); 794 applyModeButtonStyle( 795 passkeyBtn, 796 cfg.passkeyModeBtnStyle, 797 cfg.passkeyModeCustomColors, 798 !!(passkeyBtn && passkeyBtn.classList.contains('is-active')) 799 ); 800 } 801 802 function setMode(form, mode) { 803 if (isPasskeyOnlyMode()) { 804 mode = 'passkey'; 805 } 806 var isPasskey = mode === 'passkey'; 807 var isCheckout = form.classList.contains('passkeyauth-checkout-form') || isCheckoutLoginForm(form); 808 form.classList.toggle('passkeyauth-passkey-only', isPasskeyOnlyMode()); 809 810 if (isPasskey) { 811 form.classList.add('passkeyauth-password-hidden'); 812 form.classList.remove('passkeyauth-password-visible'); 813 form.dataset.passkeyauthMode = 'passkey'; 814 if (isCheckout) { 815 document.body.classList.add('passkeyauth-checkout-passkey-mode'); 816 } 817 } else { 818 form.classList.remove('passkeyauth-password-hidden'); 819 form.classList.add('passkeyauth-password-visible'); 820 form.dataset.passkeyauthMode = 'password'; 821 if (isCheckout) { 822 document.body.classList.remove('passkeyauth-checkout-passkey-mode'); 823 } 824 } 825 826 updateToggleState(form, mode); 827 syncCheckoutLoginButtonVisibility(form, isPasskey); 828 829 if (!isPasskey) { 830 var password = findPasswordInput(form); 831 if (password) { 832 password.focus(); 833 } 834 } else { 835 var emailInput = findEmailInput(form); 836 if (emailInput) { 837 emailInput.focus(); 838 } 839 } 840 } 841 842 function isPasskeyUiControl(el) { 843 if (!el) { 844 return false; 845 } 846 if (el.classList && ( 847 el.classList.contains('passkeyauth-signin-btn') 848 || el.classList.contains('passkeyauth-client-signin-btn') 849 || el.classList.contains('passkeyauth-admin-signin-btn') 850 || el.classList.contains('passkeyauth-mode-btn') 851 )) { 852 return true; 853 } 854 return !!(el.closest 855 && el.closest('.passkeyauth-client-passkey-block, .passkeyauth-admin-passkey-block, .passkeyauth-passkey-section, .passkeyauth-login-choice, .passkeyauth-auth-method-wrap')); 856 } 857 858 function looksLikeLoginControl(el) { 859 if (!el || isCompleteOrderControl(el) || isPasskeyUiControl(el)) { 860 return false; 861 } 862 863 var id = String(el.id || '').toLowerCase(); 864 var name = String(el.getAttribute('name') || '').toLowerCase(); 865 var text = String((el.value || '') + ' ' + (el.textContent || '')).replace(/\s+/g, ' ').trim().toLowerCase(); 866 867 if ( 868 id === 'btnlogin' 869 || id === 'btnexistinglogin' 870 || id.indexOf('btnlogin') !== -1 871 || id === 'login' 872 || id === 'existingloginbutton' 873 || name === 'login' 874 || name === 'loginsubmit' 875 || text === 'login' 876 || text === 'log in' 877 || text === 'sign in' 878 || text.indexOf('login') === 0 879 ) { 880 return true; 881 } 882 883 if (el.classList && el.classList.contains('passkeyauth-native-submit')) { 884 return true; 885 } 886 887 return !!(el.matches && el.matches( 888 'button#btnExistingLogin, #btnExistingLogin, button[type="submit"], input[type="submit"], button#btnLogin, input#btnLogin, button[name="login"], input[name="login"]' 889 )); 890 } 891 892 function collectCheckoutLoginButtons(form) { 893 var list = []; 894 var seen = []; 895 var stack = form.querySelector('.passkeyauth-checkout-fields'); 896 897 function add(el) { 898 if (!el || seen.indexOf(el) !== -1 || isPasskeyUiControl(el) || isCompleteOrderControl(el)) { 899 return; 900 } 901 902 // if they passed the inner span, use the actual button 903 if (el.id === 'existingLoginButton' && el.closest) { 904 var parentBtn = el.closest('#btnExistingLogin, button, .btn'); 905 if (parentBtn) { 906 el = parentBtn; 907 } 908 } 909 910 if (seen.indexOf(el) !== -1) { 911 return; 912 } 913 914 var inStack = !!(stack && stack.contains(el)); 915 var isAction = !!(el.matches && el.matches( 916 'button, input[type="submit"], input[type="button"], a.btn, #btnExistingLogin' 917 )); 918 919 // leftover action control in the checkout login stack is native Login 920 if (!looksLikeLoginControl(el) && !(inStack && isAction)) { 921 return; 922 } 923 924 seen.push(el); 925 list.push(el); 926 } 927 928 var existingLoginBtn = form.querySelector('#btnExistingLogin') 929 || document.getElementById('btnExistingLogin'); 930 if (existingLoginBtn) { 931 add(existingLoginBtn); 932 } 933 934 var existingLoginLabel = form.querySelector('#existingLoginButton') 935 || document.getElementById('existingLoginButton'); 936 if (existingLoginLabel) { 937 add(existingLoginLabel); 938 } 939 940 var primary = findNativeSubmit(form); 941 if (primary) { 942 add(primary); 943 } 944 945 if (form.id) { 946 var external = document.querySelectorAll( 947 'button[form="' + form.id + '"], input[form="' + form.id + '"]' 948 ); 949 for (var e = 0; e < external.length; e++) { 950 add(external[e]);
951 } 952 } 953 954 var roots = stack ? [stack, form] : [form]; 955 var existingBox = form.closest('#containerExistingUserSignin, .already-registered'); 956 if (existingBox) { 957 roots.push(existingBox); 958 } 959 960 for (var r = 0; r < roots.length; r++) { 961 var candidates = roots[r].querySelectorAll( 962 'button, input[type="submit"], input[type="button"], a.btn, #btnExistingLogin, #existingLoginButton' 963 ); 964 for (var i = 0; i < candidates.length; i++) { 965 add(candidates[i]); 966 } 967 } 968 969 return list; 970 } 971 972 function syncCheckoutLoginButtonVisibility(form, isPasskey) { 973 if (!form || !form.classList.contains('passkeyauth-checkout-form')) { 974 return; 975 } 976 977 wrapNativeSubmit(form); 978 979 var loginBtns = collectCheckoutLoginButtons(form); 980 for (var i = 0; i < loginBtns.length; i++) { 981 var loginBtn = loginBtns[i]; 982 if (!loginBtn || isCompleteOrderControl(loginBtn)) { 983 continue; 984 } 985 986 loginBtn.classList.add('passkeyauth-native-submit'); 987 var wrap = loginBtn.closest('.passkeyauth-native-submit-wrap'); 988 989 if (isPasskey) { 990 loginBtn.style.setProperty('display', 'none', 'important'); 991 loginBtn.style.setProperty('visibility', 'hidden', 'important'); 992 loginBtn.setAttribute('aria-hidden', 'true'); 993 loginBtn.setAttribute('tabindex', '-1'); 994 if (wrap) { 995 wrap.style.setProperty('display', 'none', 'important'); 996 wrap.style.setProperty('visibility', 'hidden', 'important'); 997 } 998 } else { 999 loginBtn.style.removeProperty('display'); 1000 loginBtn.style.removeProperty('visibility'); 1001 loginBtn.removeAttribute('aria-hidden'); 1002 loginBtn.removeAttribute('tabindex'); 1003 if (wrap) { 1004 wrap.style.removeProperty('display'); 1005 wrap.style.removeProperty('visibility'); 1006 } 1007 } 1008 } 1009 1010 // Complete Order stays visible even if it got our class by accident 1011 var completeBtns = form.querySelectorAll( 1012 '#btnCompleteOrder, button[id*="CompleteOrder"], input[id*="CompleteOrder"], .btn-checkout' 1013 ); 1014 for (var c = 0; c < completeBtns.length; c++) { 1015 var btn = completeBtns[c]; 1016 btn.classList.remove('passkeyauth-native-submit'); 1017 btn.style.removeProperty('display'); 1018 btn.style.removeProperty('visibility'); 1019 btn.removeAttribute('aria-hidden'); 1020 btn.removeAttribute('tabindex'); 1021 var cWrap = btn.closest('.passkeyauth-native-submit-wrap'); 1022 if (cWrap && cWrap.querySelector('#btnCompleteOrder, [id*="CompleteOrder"], .btn-checkout')) { 1023 cWrap.classList.remove('passkeyauth-native-submit-wrap'); 1024 cWrap.style.removeProperty('display'); 1025 cWrap.style.removeProperty('visibility'); 1026 } 1027 } 1028 } 1029 1030 function runPasskeyLogin(form, button) { 1031 var emailInput = findEmailInput(form); 1032 var identifier = emailInput && emailInput.value ? emailInput.value.trim() : ''; 1033 if (!identifier && !allowsUsernameless()) { 1034 identifier = window.prompt(cfg.emailPrompt, '') || ''; 1035 if (identifier && emailInput) { 1036 emailInput.value = identifier; 1037 } 1038 } 1039 if (!identifier && !allowsUsernameless()) { 1040 return; 1041 } 1042 1043 if (identifier) { 1044 rememberEmail(identifier); 1045 } 1046 button.disabled = true; 1047 1048 var finishPayload = { 1049 csrfToken: cfg.csrfToken, 1050 challengeToken: null, 1051 credential: null 1052 }; 1053 1054 postJson('api_login_begin', { 1055 csrfToken: cfg.csrfToken, 1056 identifier: identifier || '' 1057 }).then(function (begin) { 1058 if (!begin.ok) { 1059 throw new Error(begin.message || cfg.errorMessage); 1060 } 1061 1062 var options = begin.publicKey || {}; 1063 options.challenge = b64urlToBuffer(options.challenge); 1064 options.allowCredentials = (options.allowCredentials || []).map(function (item) { 1065 item.id = b64urlToBuffer(item.id); 1066 return item; 1067 }); 1068 1069 var choose = window.PasskeyAuthWebAuthn && window.PasskeyAuthWebAuthn.choosePasskey 1070 ? window.PasskeyAuthWebAuthn.choosePasskey 1071 : function (passkeys) { 1072 return Promise.resolve(passkeys && passkeys[0] ? passkeys[0].id : ''); 1073 }; 1074 1075 return choose(begin.passkeys || [], cfg).then(function (selectedId) { 1076 if (window.PasskeyAuthWebAuthn && window.PasskeyAuthWebAuthn.restrictAllowCredentials) { 1077 options = window.PasskeyAuthWebAuthn.restrictAllowCredentials(options, selectedId); 1078 } else if (selectedId) { 1079 options.allowCredentials = [{ 1080 type: 'public-key', 1081 id: b64urlToBuffer(selectedId), 1082 transports: ['internal', 'hybrid', 'usb', 'nfc', 'ble'] 1083 }]; 1084 } else { 1085 options.allowCredentials = []; 1086 } 1087 1088 return navigator.credentials.get({ publicKey: options }); 1089 }).then(function (cred) { 1090 if (!cred) { 1091 throw new Error('Passkey authentication was cancelled.'); 1092 } 1093 1094 if (window.PasskeyAuthWebAuthn && typeof window.PasskeyAuthWebAuthn.showLoader === 'function') { 1095 window.PasskeyAuthWebAuthn.showLoader('Verifying Passkey...'); 1096 } 1097 1098 finishPayload.challengeToken = begin.challengeToken; 1099 finishPayload.credential = { 1100 id: cred.id, 1101 rawId: bufferToB64url(cred.rawId), 1102 type: cred.type, 1103 response: { 1104 clientDataJSON: bufferToB64url(cred.response.clientDataJSON), 1105 authenticatorData: bufferToB64url(cred.response.authenticatorData), 1106 signature: bufferToB64url(cred.response.signature), 1107 userHandle: cred.response.userHandle ? bufferToB64url(cred.response.userHandle) : '' 1108 } 1109 }; 1110 1111 // checkout: send them back to cart after SSO 1112 if (form.classList.contains('passkeyauth-checkout-form') || isCheckoutLoginForm(form)) { 1113 var redirectPath = getCheckoutSsoRedirectPath(); 1114 if (redirectPath) { 1115 finishPayload.sso_redirect_path = redirectPath; 1116 } 1117 } 1118 1119 return postJson('api_login_finish', finishPayload); 1120 }); 1121 }).then(function (finish) { 1122 if (!finish || !finish.ok) { 1123 if (window.PasskeyAuthWebAuthn && typeof window.PasskeyAuthWebAuthn.hideLoader === 'function') { 1124 window.PasskeyAuthWebAuthn.hideLoader(); 1125 } 1126 throw new Error((finish && finish.message) || cfg.errorMessage); 1127 } 1128 setLocalPasskeyHint(identifier); 1129 // SSO token is one-shot â created on the complete page, not in this JSON. 1130 var completeUrl = finish.completeUrl; 1131 if (typeof completeUrl !== 'string' || completeUrl.indexOf('complete_login') < 0) { 1132 var endpoint = cfg.endpoint || 'index.php?m=passkeyauth'; 1133 completeUrl = endpoint + (endpoint.indexOf('?') >= 0 ? '&' : '?') + 'action=complete_login'; 1134 } 1135 window.location.replace(completeUrl); 1136 }).catch(function (error) { 1137 if (window.PasskeyAuthWebAuthn && typeof window.PasskeyAuthWebAuthn.hideLoader === 'function') { 1138 window.PasskeyAuthWebAuthn.hideLoader(); 1139 } 1140 button.disabled = false; 1141 // user hit cancel on the WebAuthn prompt 1142 if (error && (error.name === 'NotAllowedError' || error.
1142name === 'AbortError')) { 1143 return; 1144 } 1145 showLoginError((error && error.message) ? error.message : ''); 1146 }); 1147 } 1148 1149 var KEY_ICON = 1150 '<svg class="passkeyauth-mode-icon passkeyauth-btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + 1151 '<circle cx="7.5" cy="15.5" r="4.5"></circle>' + 1152 '<path d="M10.7 12.3L19 4"></path>' + 1153 '<path d="M15.5 7.5L18 10"></path>' + 1154 '<path d="M13 10L15 12"></path>' + 1155 '</svg>'; 1156 1157 var LOCK_ICON = KEY_ICON; 1158 1159 var MICROSOFT_ICON = 1160 '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" style="flex-shrink:0;vertical-align:middle;display:inline-block;" aria-hidden="true">' + 1161 '<path fill="#f25022" d="M1 1h10v10H1z"/>' + 1162 '<path fill="#7fba00" d="M13 1h10v10H13z"/>' + 1163 '<path fill="#00a4ef" d="M1 13h10v10H1z"/>' + 1164 '<path fill="#ffb900" d="M13 13h10v10H13z"/>' + 1165 '</svg>'; 1166 1167 var MOJEID_ICON = 1168 '<svg width="18" height="14" viewBox="0 0 48 37" fill="none" xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0;vertical-align:middle;display:inline-block;" aria-hidden="true">' + 1169 '<path d="M31.2911 1.09375H17.6641V20.4158C19.8387 18.8503 22.1551 17.4921 24.5831 16.3587V8.01075H31.2911V8.02375C33.8842 8.02375 36.3712 9.05388 38.2048 10.8875C40.0384 12.7212 41.0686 15.2081 41.0686 17.8013C41.0686 20.3944 40.0384 22.8813 38.2048 24.715C36.3712 26.5486 33.8842 27.5788 31.2911 27.5788V27.5847H24.5831V21.5478C24.5831 21.3748 24.4261 21.3767 24.3471 21.4347C21.8306 23.186 19.5797 25.291 17.6641 27.6847V34.5097H31.2911C35.7222 34.5097 39.9718 32.7495 43.105 29.6162C46.2383 26.483 47.9986 22.2334 47.9986 17.8022C47.9986 13.3711 46.2383 9.12153 43.105 5.98826C39.9718 2.855 35.7222 1.09475 31.2911 1.09475V1.09375Z" fill="currentColor"/>' + 1170 '<path d="M24.5831 16.3587L17.6641 20.4158V1.09375L24.5831 8.01075V16.3587Z" fill="url(#mojeid-g0)"/>' + 1171 '<path d="M10.9699 11.3828H4.33594V34.4838H10.9699V11.3828Z" fill="url(#mojeid-g1)"/>' + 1172 '<path d="M38.1881 13.2576C38.1833 13.0709 38.1125 12.8919 37.9881 12.7526C37.8759 12.6588 37.7432 12.5927 37.6008 12.5595C37.4584 12.5264 37.3102 12.5271 37.1681 12.5616C30.325 13.2722 23.7581 15.6399 18.036 19.4596C12.3138 23.2794 7.60877 28.4362 4.32812 34.4836H10.9621C17.9991 22.0916 28.3311 16.4936 37.6531 13.8746C38.0691 13.7366 38.1971 13.4836 38.1881 13.2576Z" fill="url(#mojeid-g2)"/>' + 1173 '<path d="M7.64869 0.683594C8.43373 0.683594 9.20113 0.916397 9.85385 1.35256C10.5066 1.78872 11.0153 2.40866 11.3157 3.13395C11.616 3.85925 11.6946 4.65734 11.5414 5.42728C11.3881 6.19721 11.01 6.90442 10.4548 7.45945C9.89967 8.01449 9.19236 8.39242 8.42239 8.54545C7.65241 8.69848 6.85435 8.61974 6.12913 8.31918C5.40391 8.01863 4.7841 7.50975 4.3481 6.85692C3.9121 6.20409 3.67949 5.43663 3.67969 4.65159C3.68048 3.59929 4.09893 2.59032 4.84312 1.84632C5.58731 1.10232 6.59638 0.684123 7.64869 0.683594Z" fill="#FFCB00"/>' + 1174 '<defs>' + 1175 '<linearGradient id="mojeid-g0" x1="22.88" y1="17.98" x2="15.48" y2="2.11" gradientUnits="userSpaceOnUse"><stop offset="0.006" stop-color="#222"/><stop offset="0.393" stop-color="#888"/></linearGradient>' + 1176 '<linearGradient id="mojeid-g1" x1="14.71" y1="31.34" x2="0.59" y2="14.52" gradientUnits="userSpaceOnUse"><stop stop-color="#DF8200"/><stop offset="0.392" stop-color="#F0A800"/><stop offset="0.713" stop-color="#FFCB00"/><stop offset="1" stop-color="#FFCB00"/></linearGradient>' + 1177 '<linearGradient id="mojeid-g2" x1="4.33" y1="23.52" x2="38.19" y2="23.52" gradientUnits="userSpaceOnUse"><stop stop-color="#FCC200"/><stop offset="1" stop-color="#FFCB00"/></linearGradient>' + 1178 '</defs>' + 1179 '</svg>'; 1180 1181 function emailFieldGroup(form) { 1182 var emailInput = findEmailInput(form); 1183 if (!emailInput) { 1184 return null; 1185 } 1186 1187 return emailInput.closest('.passkeyauth-checkout-email-group, .form-group') || emailInput.parentElement; 1188 } 1189 1190 function insertAfterEmailGroup(form, element) { 1191 var emailGroup = emailFieldGroup(form); 1192 if (emailGroup && emailGroup.parentNode) { 1193 if (emailGroup.nextSibling) { 1194 emailGroup.parentNode.insertBefore(element, emailGroup.nextSibling); 1195 } else { 1196 emailGroup.parentNode.appendChild(element); 1197 } 1198 return; 1199 } 1200 1201 form.appendChild(element); 1202 } 1203 1204 function normalizeEmail(value) { 1205 return String(value || '').trim().toLowerCase(); 1206 } 1207 1208 function getLocalPasskeyHint(email) { 1209 var normalized = normalizeEmail(email); 1210 if (!normalized) { 1211 return false; 1212 } 1213 1214 try { 1215 var map = JSON.parse(window.localStorage.getItem(PASSKEY_DEVICE_STORAGE_KEY) || '{}'); 1216 return !!map[normalized]; 1217 } catch (e) { 1218 return false; 1219 } 1220 } 1221 1222 function setLocalPasskeyHint(email) { 1223 var normalized = normalizeEmail(email); 1224 if (!normalized) { 1225 return; 1226 } 1227 1228 try { 1229 var map = JSON.parse(window.localStorage.getItem(PASSKEY_DEVICE_STORAGE_KEY) || '{}'); 1230 map[normalized] = true; 1231 window.localStorage.setItem(PASSKEY_DEVICE_STORAGE_KEY, JSON.stringify(ma
1231p)); 1232 } catch (e) { 1233 // Ignore storage failures. 1234 } 1235 } 1236 1237 function updateAuthMethodVisibility(form, visible) { 1238 var wrap = form.querySelector('.passkeyauth-auth-method-wrap'); 1239 if (!wrap) { 1240 return; 1241 } 1242 1243 wrap.classList.add('passkeyauth-auth-method-wrap'); 1244 wrap.classList.remove('passkeyauth-auth-method-hidden'); 1245 form.dataset.passkeyauthPasskeyAvailable = '1'; 1246 } 1247 1248 function hasDevicePasskeyForAccount(email) { 1249 return getLocalPasskeyHint(email); 1250 } 1251 1252 function getUserModeChoice(form, email) { 1253 var choiceEmail = form.dataset.passkeyauthUserModeEmail || ''; 1254 if (choiceEmail !== normalizeEmail(email)) { 1255 return null; 1256 } 1257 var mode = form.dataset.passkeyauthUserMode || ''; 1258 return mode === 'passkey' || mode === 'password' ? mode : null; 1259 } 1260 1261 function setUserModeChoice(form, mode, email) { 1262 form.dataset.passkeyauthUserMode = mode; 1263 form.dataset.passkeyauthUserModeEmail = normalizeEmail(email); 1264 } 1265 1266 function clearUserModeChoice(form) { 1267 delete form.dataset.passkeyauthUserMode; 1268 delete form.dataset.passkeyauthUserModeEmail; 1269 } 1270 1271 function applyPasskeyAvailability(form, hasPasskeys, email) { 1272 if (isPasskeyOnlyMode()) { 1273 updateAuthMethodVisibility(form, true); 1274 setMode(form, 'passkey'); 1275 form.dataset.passkeyauthCheckedEmail = normalizeEmail(email); 1276 return; 1277 } 1278 1279 updateAuthMethodVisibility(form, hasPasskeys || allowsUsernameless()); 1280 1281 var normalizedEmail = normalizeEmail(email); 1282 var previousEmail = form.dataset.passkeyauthCheckedEmail || ''; 1283 if (normalizedEmail !== previousEmail) { 1284 clearUserModeChoice(form); 1285 } 1286 1287 var userChoice = getUserModeChoice(form, email); 1288 if (userChoice) { 1289 setMode(form, userChoice); 1290 } else { 1291 var preferPasskey = hasPasskeys && ( 1292 cfg.authMode === 'passkey_only' 1293 || cfg.authMode === 'passkey_preferred' 1294 || hasDevicePasskeyForAccount(email) 1295 ); 1296 setMode(form, preferPasskey ? 'passkey' : 'password'); 1297 } 1298 1299 form.dataset.passkeyauthCheckedEmail = normalizedEmail; 1300 } 1301 1302 function checkPasskeyAvailability(form, email) { 1303 email = normalizeEmail(email); 1304 if (!email || email.indexOf('@') < 1) { 1305 applyPasskeyAvailability(form, false, ''); 1306 return Promise.resolve(false); 1307 } 1308 1309 if (form.dataset.passkeyauthChecking === email) { 1310 return Promise.resolve(form.dataset.passkeyauthPasskeyAvailable === '1'); 1311 } 1312 1313 form.dataset.passkeyauthChecking = email; 1314 1315 return postJson('api_login_passkey_available', { 1316 csrfToken: cfg.csrfToken, 1317 identifier: email 1318 }).then(function (res) { 1319 var hasPasskeys = !!(res && res.ok && res.hasPasskeys); 1320 applyPasskeyAvailability(form, hasPasskeys, email); 1321 return hasPasskeys; 1322 }).catch(function () { 1323 applyPasskeyAvailability(form, false, email); 1324 return false; 1325 }).then(function (result) { 1326 delete form.dataset.passkeyauthChecking; 1327 return result; 1328 }); 1329 } 1330 1331 function schedulePasskeyAvailabilityCheck(form) { 1332 var emailInput = findEmailInput(form); 1333 if (!emailInput) { 1334 return; 1335 } 1336 1337 var run = function () { 1338 checkPasskeyAvailability(form, emailInput.value || ''); 1339 }; 1340 1341 if (availabilityTimers && availabilityTimers.has(form)) { 1342 clearTimeout(availabilityTimers.get(form)); 1343 } 1344 1345 var timer = setTimeout(run, 350); 1346 if (availabilityTimers) { 1347 availabilityTimers.set(form, timer); 1348 } 1349 } 1350 1351 function buildAuthMethodWrap(form) { 1352 if (form.querySelector('.passkeyauth-auth-method-wrap')) { 1353 return form.querySelector('.passkeyauth-auth-method-wrap'); 1354 } 1355 1356 var wrap = document.createElement('div'); 1357 wrap.className = 'passkeyauth-auth-method-wrap'; 1358 1359 var choice = document.createElement('div'); 1360 choice.className = 'passkeyauth-login-choice'; 1361 choice.setAttribute('role', 'group'); 1362 choice.setAttribute('aria-label', cfg.authMethodLabel || 'Authentication method'); 1363 1364 var passwordBtn = document.createElement('button'); 1365 passwordBtn.type = 'button'; 1366 passwordBtn.className = 'btn btn-block passkeyauth-mode-btn passkeyauth-mode-password'; 1367 passwordBtn.innerHTML = LOCK_ICON + '<span>' + (cfg.passwordModeLabel || 'Login with Password') + '</span>'; 1368 passwordBtn.addEventListener('click', function () { 1369 if (isPasskeyOnlyMode()) { 1370 return; 1371 } 1372 var emailInput = findEmailInput(form); 1373 var email = emailInput && emailInput.value ? emailInput.value.trim() : ''; 1374 setUserModeChoice(form, 'password', email); 1375 setMode(form, 'password'); 1376 }); 1377 1378 var passkeyBtn = document.createElement('button'); 1379 passkeyBtn.type = 'button'; 1380 passkeyBtn.className = 'btn btn-block passkeyauth-mode-btn passkeyauth-mode-passkey'; 1381 passkeyBtn.innerHTML = KEY_ICON + '<span>' + (cfg.passkeyModeLabel || 'Sign in with Passkey') + '</span>'; 1382 passkeyBtn.addEventListener('click', function () { 1383 var emailInput = findEmailInput(form); 1384 var email = emailInput && emailInput.value ? emailInput.value.trim() : ''; 1385 if (!email && !allowsUsernameless()) { 1386 if (emailInput) { 1387 emailInput.focus(); 1388 } 1389 return; 1390 } 1391 1392 setUserModeChoice(form, 'passkey', email); 1393 setMode(form, 'passkey'); 1394 runPasskeyLogin(form, passkeyBtn); 1395 }); 1396 1397 if (!isPasskeyOnlyMode()) { 1398 choice.appendChild(passwordBtn); 1399 } 1400 if (passkeyUiEnabled) { 1401 choice.appendChild(passkeyBtn); 1402 } 1403 if (choice.childNodes.length) { 1404 wrap.appendChild(choice); 1405 } 1406 1407 var hasMs = !!(cfg.microsoftSsoEnabled && cfg.microsoftAuthUrl); 1408 var hasMoje = !!(cfg.mojeidSsoEnabled && cfg.mojeidAuthUrl); 1409 1410 if (hasMs || hasMoje) { 1411 var isBothSso = hasMs && hasMoje; 1412 var ssoRow = document.createElement('div'); 1413 ssoRow.className = 'passkeyauth-sso-row' + (isBothSso ? ' passkeyauth-sso-dual' : ' passkeyauth-sso-single'); 1414 ssoRow.style.marginTop = '10px'; 1415 ssoRow.style.width = '100%'; 1416 ssoRow.style.boxSizing = 'border-box'; 1417 if (isBothSso) { 1418 ssoRow.style.display = 'flex'; 1419 ssoRow.style.gap = '8px'; 1420 ssoRow.style.alignItems = 'stretch'; 1421 } 1422 1423 if (hasMs) { 1424 var msWrap = document.createElement('div'); 1425 msWrap.className = 'passkeyauth-ms-login-wrap'; 1426 msWrap.style.boxSizing = 'border-box'; 1427 if (isBothSso) { 1428 msWrap.style.flex = '1 1 0'; 1429 msWrap.style.width = '50%'; 1430 msWrap.style.minWidth = '0'; 1431 msWrap.style.margin = '0'; 1432 } else { 1433 msWrap.style.width = '100%'; 1434 msWrap.style.margin = '0'; 1435 } 1436 1437 var msBtn = document.createElement('a'); 1438 1439 function getMicrosoftAuthTargetUrl() { 1440 var baseAuthUrl = cfg.microsoftAuthUrl; 1441 if (baseAuthUrl.indexOf('redirect=') !== -1) { 1442 return baseAuthUrl; 1443 } 1444 var target = ''; 1445 if (form.classList.contains('passkeyauth-checkout-form') || isCheckoutLoginForm(form) || window.location.href.indexOf('cart.php') !== -1 || window.location.search.indexOf('a=checkout') !== -1) { 1446 target = getCheckoutSsoRedirectPath() || 'cart.php?a=checkout'; 1447 } else { 1448 var search = window.location.search || ''; 1449 var match = search.match(/[?&](?:redirect|returnurl|rp)=([^&]+)/i); 1450 if (match && match[1]) { 1451 target = decodeURIComponent(match[1]); 1452 } 1453 } 1454 if (target) { 1455 return baseAuthUrl + (baseAuthUrl.indexOf('?') >= 0 ? '&' : '?') + 'redirect=' + encodeURIComponent(target); 1456 } 1457 return baseAuthUrl; 1458 } 1459 1460 msBtn.href = getMicrosoftAuthTargetUrl(); 1461 msBtn.addEventListener('click', function () { 1462 msBtn.href = getMicrosoftAuthTargetUrl(); 1463 }); 1464 msBtn.className = 'btn btn-block passkeyauth-ms-btn'; 1465 msBtn.title = cfg.microsoftButtonLabel || 'Sign in with Microsoft'; 1466 msBtn.style.display = 'flex'; 1467 msBtn.style.alignItems = 'center'; 1468 msBtn.style.justifyContent = 'center'; 1469 msBtn.style.gap = '8px'; 1470 msBtn.style.background = '#2f2f2f'; 1471 msBtn.style.color = '#ffffff'; 1472 msBtn.style.fontWeight = '600'; 1473 msBtn.style.fontSize = '13px'; 1474 msBtn.style.padding = '8px 14px'; 1475 msBtn.style.borderRadius = '6px';
1476 msBtn.style.textDecoration = 'none'; 1477 msBtn.style.border = '1px solid #1f1f1f'; 1478 msBtn.style.boxSizing = 'border-box'; 1479 msBtn.style.width = '100%'; 1480 msBtn.style.height = '100%'; 1481 msBtn.style.margin = '0'; 1482 var msLabel = isBothSso ? 'Microsoft' : (cfg.microsoftButtonLabel || 'Sign in with Microsoft'); 1483 msBtn.innerHTML = MICROSOFT_ICON + '<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + msLabel + '</span>'; 1484 msWrap.appendChild(msBtn); 1485 ssoRow.appendChild(msWrap); 1486 } 1487 1488 if (hasMoje) { 1489 var mojeWrap = document.createElement('div'); 1490 mojeWrap.className = 'passkeyauth-mojeid-login-wrap'; 1491 mojeWrap.style.boxSizing = 'border-box'; 1492 if (isBothSso) { 1493 mojeWrap.style.flex = '1 1 0'; 1494 mojeWrap.style.width = '50%'; 1495 mojeWrap.style.minWidth = '0'; 1496 mojeWrap.style.margin = '0'; 1497 } else { 1498 mojeWrap.style.width = '100%'; 1499 mojeWrap.style.margin = '0'; 1500 } 1501 1502 var mojeBtn = document.createElement('a'); 1503 1504 function getMojeIdAuthTargetUrl() { 1505 var baseAuthUrl = cfg.mojeidAuthUrl; 1506 if (baseAuthUrl.indexOf('redirect=') !== -1) { 1507 return baseAuthUrl; 1508 } 1509 var target = ''; 1510 if (form.classList.contains('passkeyauth-checkout-form') || isCheckoutLoginForm(form) || window.location.href.indexOf('cart.php') !== -1 || window.location.search.indexOf('a=checkout') !== -1) { 1511 target = getCheckoutSsoRedirectPath() || 'cart.php?a=checkout'; 1512 } else { 1513 var search = window.location.search || ''; 1514 var match = search.match(/[?&](?:redirect|returnurl|rp)=([^&]+)/i); 1515 if (match && match[1]) { 1516 target = decodeURIComponent(match[1]); 1517 } 1518 } 1519 if (target) { 1520 return baseAuthUrl + (baseAuthUrl.indexOf('?') >= 0 ? '&' : '?') + 'redirect=' + encodeURIComponent(target); 1521 } 1522 return baseAuthUrl; 1523 } 1524 1525 mojeBtn.href = getMojeIdAuthTargetUrl(); 1526 mojeBtn.addEventListener('click', function () { 1527 mojeBtn.href = getMojeIdAuthTargetUrl(); 1528 }); 1529 mojeBtn.className = 'btn btn-block passkeyauth-mojeid-btn passkeyauth-btn-style-' + (cfg.mojeidButtonStyle || 'dark'); 1530 mojeBtn.title = cfg.mojeidButtonLabel || 'Sign in with MojeID'; 1531 mojeBtn.style.display = 'flex'; 1532 mojeBtn.style.alignItems = 'center'; 1533 mojeBtn.style.justifyContent = 'center'; 1534 mojeBtn.style.gap = '8px'; 1535 mojeBtn.style.fontWeight = '600'; 1536 mojeBtn.style.fontSize = '13px'; 1537 mojeBtn.style.padding = '8px 14px'; 1538 mojeBtn.style.borderRadius = '6px'; 1539 mojeBtn.style.textDecoration = 'none'; 1540 mojeBtn.style.boxSizing = 'border-box'; 1541 mojeBtn.style.width = '100%'; 1542 mojeBtn.style.height = '100%'; 1543 mojeBtn.style.margin = '0'; 1544 if (!cfg.mojeidButtonStyle || cfg.mojeidButtonStyle === 'dark') { 1545 mojeBtn.style.background = '#c8102e'; 1546 mojeBtn.style.color = '#ffffff'; 1547 mojeBtn.style.border = '1px solid #a00d25'; 1548 } 1549 1550 var mojeLabel = isBothSso ? 'MojeID' : (cfg.mojeidButtonLabel || 'Sign in with MojeID'); 1551 mojeBtn.innerHTML = MOJEID_ICON + '<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + mojeLabel + '</span>'; 1552 mojeWrap.appendChild(mojeBtn); 1553 ssoRow.appendChild(mojeWrap); 1554 } 1555 1556 wrap.appendChild(ssoRow); 1557 } 1558 1559 if (isPasskeyOnlyMode()) { 1560 wrap.classList.add('passkeyauth-passkey-only'); 1561 wrap.classList.remove('passkeyauth-auth-method-hidden'); 1562 } else if (allowsUsernameless()) { 1563 wrap.classList.remove('passkeyauth-auth-method-hidden'); 1564 } 1565 insertAfterEmailGroup(form, wrap); 1566 refreshModeButtonStyles(form); 1567 1568 return wrap; 1569 } 1570 1571 function bindEmailRemember(form) { 1572 var emailInput = findEmailInput(form); 1573 if (!emailInput || emailInput.dataset.passkeyauthEmailBound === '1') { 1574 return; 1575 } 1576 emailInput.dataset.passkeyauthEmailBound = '1'; 1577 1578 emailInput.addEventListener('change', function () { 1579 rememberEmail((emailInput.value || '').trim()); 1580 }); 1581 emailInput.addEventListener('blur', function () { 1582 rememberEmail((emailInput.value || '').trim()); 1583 }); 1584 } 1585 1586 function setupForm(form) { 1587 if (!form || form.dataset.passkeyInjected === '1') { 1588 return; 1589 } 1590 if (isRegistrationForm(form) && !hasCheckoutExistingLogin(form) && !isCheckoutLoginForm(form)) { 1591 return; 1592 } 1593 form.dataset.passkeyInjected = '1'; 1594 1595 var checkoutForm = isCheckoutLoginForm(form); 1596 if (checkoutForm) { 1597 document.body.classList.add('passkeyauth-checkout-login-active'); 1598 normalizeCheckoutLayout(form); 1599 } 1600 1601 var group = passwordGroup(form); 1602 if (group) { 1603 group.classList.add('passkeyauth-password-group'); 1604 } 1605 1606 if (checkoutForm) { 1607 wrapNativeSubmit(form); 1608 } else { 1609 var submit = findNativeSubmit(form); 1610 if (submit) { 1611 submit.classList.add('passkeyauth-native-submit'); 1612 } 1613 } 1614 1615 restoreRememberedEmail(form); 1616 bindEmailRemember(form); 1617 1618 var formToken = getFormCsrfToken(form); 1619 if (formToken && (!cfg.csrfToken || cfg.csrfToken !== formToken)) { 1620 cfg.csrfToken = formToken; 1621 } 1622 1623 buildAuthMethodWrap(form); 1624 if (passkeyUiEnabled && isPasskeyOnlyMode()) { 1625 form.classList.add('passkeyauth-passkey-only'); 1626 setMode(form, 'passkey'); 1627 if (form.dataset.passkeyauthPasskeyOnlySubmit !== '1') { 1628 form.dataset.passkeyauthPasskeyOnlySubmit = '1'; 1629 form.addEventListener('submit', function (e) { 1630 e.preventDefault(); 1631 e.stopPropagation(); 1632 runPasskeyLogin(form, form.querySelector('.passkeyauth-mode-passkey')); 1633 }, true); 1634 } 1635 } else { 1636 setMode(form, 'password'); 1637 } 1638 1639 if (checkoutForm) { 1640 reorderCheckoutFields(form); 1641 } 1642 } 1643 1644 function injectButtons() { 1645 if (document.body) { 1646 document.body.classList.remove('passkeyauth-admin-login-active'); 1647 document.body.classList.add('passkeyauth-client-login-active'); 1648 } 1649 1650 var path = (window.location.pathname || '').toLowerCase(); 1651 var search = (window.location.search || '').toLowerCase(); 1652 var isCheckoutPage = path.indexOf('cart.php') !== -1 1653 || path.indexOf('/cart/') !== -1 1654 || path.slice(-5) === '/cart' 1655 || search.indexOf('a=checkout') !== -1 1656 || search.indexOf('rp=/cart') !== -1 1657 || search.indexOf('rp=%2fcart') !== -1 1658 || !!document.getElementById('frmCheckout') 1659 || !!document.getElementById('containerExistingUserSignin'); 1660 if (!isCheckoutPage && (path.indexOf('register') !== -1 || search.indexOf('register') !== -1 || search.indexOf('rp=/register') !== -1)) { 1661 return; 1662 } 1663 1664 var forms = document.querySelectorAll('form'); 1665 var firstLoginForm = null; 1666 for (var i = 0; i < forms.length; i++) { 1667 var form = forms[i]; 1668 if (isRegistrationForm(form) && !hasCheckoutExistingLogin(form) && !isCheckoutLoginForm(form)) { 1669 continue; 1670 } 1671 var hasPassword = form.querySelector('input[type="password"], input[name="loginpw"]'); 1672 if (!hasPassword) { 1673 continue; 1674 } 1675 if (!findEmailInput(form)) { 1676 continue; 1677 } 1678 if (!firstLoginForm) { 1679 firstLoginForm = form; 1680 } 1681 setupForm(form); 1682 } 1683 1684 var existingBox = document.getElementById('containerExistingUserSignin'); 1685 if (existingBox) { 1686 var hostForm = existingBox.closest('form'); 1687 if (hostForm) { 1688 if (!firstLoginForm) { 1689 firstLoginForm = hostForm; 1690 } 1691 setupForm(hostForm); 1692 } 1693 } 1694 1695 showLoginFlash(firstLoginForm); 1696 } 1697 1698 if (document.readyState === 'loading') { 1699 document.addEventListener('DOMContentLoaded', injectButtons); 1700 } else { 1701 injectButtons(); 1702 } 1703 1704 if (window.MutationObserver) { 1705 var observer = new MutationObserver(function () { 1706 injectButtons(); 1707 }); 1708 observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
1709 } 1710 1711 window.PasskeyAuthDeviceHint = { 1712 mark: setLocalPasskeyHint 1713 }; 1714})();
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.