1/** 2 * 2FA backup code: delegated copy/download + inject toolbar when core HTML has no Smarty partial. 3 * Required because jQuery .html() does not run <script> tags, and bundled theme JS does not include this. 4 */ 5(function () { 6 'use strict'; 7 8 function whmcsTwofaBackupEnsureDelegation() { 9 if (window.whmcsTwofaBackupDelegationBound) { 10 return; 11 } 12 window.whmcsTwofaBackupDelegationBound = true; 13 document.addEventListener('click', function (ev) { 14 var copyBtn = ev.target.closest('.twofa-backup-copy'); 15 if (copyBtn) { 16 ev.preventDefault(); 17 var wrap = copyBtn.closest('.twofa-backup-code-wrap'); 18 if (!wrap) { 19 return; 20 } 21 var valEl = wrap.querySelector('.twofa-backup-code-value'); 22 if (!valEl) { 23 return; 24 } 25 var text = (valEl.textContent || '').replace(/\s+/g, ' ').trim(); 26 if (!text) { 27 return; 28 } 29 function copyText(t) { 30 if (navigator.clipboard && window.isSecureContext) { 31 return navigator.clipboard.writeText(t); 32 } 33 var x = document.createElement('textarea'); 34 x.value = t; 35 x.style.position = 'fixed'; 36 x.style.left = '-9999px'; 37 document.body.appendChild(x); 38 x.select(); 39 try { 40 return document.execCommand('copy') ? Promise.resolve() : Promise.reject(); 41 } finally { 42 document.body.removeChild(x); 43 } 44 } 45 copyText(text) 46 .then(function () { 47 if (typeof jQuery !== 'undefined' && jQuery.growl) { 48 jQuery.growl.notice({ message: 'Copied to clipboard.' }); 49 } 50 }) 51 .catch(function () { 52 window.alert('Could not copy. Please select and copy the code manually.'); 53 }); 54 return; 55 } 56 var dlBtn = ev.target.closest('.twofa-backup-download'); 57 if (dlBtn) { 58 ev.preventDefault(); 59 var wrap = dlBtn.closest('.twofa-backup-code-wrap'); 60 if (!wrap) { 61 return; 62 } 63 var valEl = wrap.querySelector('.twofa-backup-code-value'); 64 if (!valEl) { 65 return; 66 } 67 var text = (valEl.textContent || '').replace(/\s+/g, ' ').trim(); 68 if (!text) { 69 return; 70 } 71 var blob = new Blob(['WHMCS two-factor backup code\r\n\r\n' + text + '\r\n'], { 72 type: 'text/plain;charset=utf-8', 73 }); 74 var u = URL.createObjectURL(blob); 75 var a = document.createElement('a'); 76 a.href = u; 77 a.download = 'whmcs-2fa-backup-code.txt'; 78 document.body.appendChild(a); 79 a.click(); 80 document.body.removeChild(a); 81 URL.revokeObjectURL(u); 82 } 83 }); 84 } 85 86 function enhanceWhmcsModalTwoFactorBackupCode() { 87 var body = document.querySelector('#modalAjax .modal-body'); 88 if (!body || body.querySelector('.twofa-backup-code-wrap')) { 89 return; 90 } 91 var re = /^[a-f0-9]{4}(?:\s+[a-f0-9]{4}){3}$/i; 92 var candidates = body.querySelectorAll('p, div, span, strong, td, code, kbd, h1, h2, h3, h4, h5, h6'); 93 var el = null; 94 var i; 95 for (i = 0; i < candidates.length; i++) { 96 var c = candidates[i]; 97 if (c.closest('.twofa-backup-code-wrap')) { 98 continue; 99 } 100 var t = (c.textContent || '').trim().replace(/\s+/g, ' '); 101 if (re.test(t) && t.length < 48) { 102 el = c; 103 break; 104 } 105 } 106 if (!el) { 107 return; 108 } 109 var codeText = el.textContent.trim().replace(/\s+/g, ' '); 110 el.style.display = 'none'; 111 el.classList.add('twofa-backup-code-enhanced-source'); 112 113 var toolbar = document.createElement('div'); 114 toolbar.className = 'alert alert-success text-center twofa-backup-code-wrap'; 115 toolbar.setAttribute('role', 'region'); 116 117 var inner = document.createElement('div'); 118 inner.className = 'd-flex align-items-center justify-content-center flex-wrap'; 119 inner.style.gap = '10px'; 120 121 var val = document.createElement('span'); 122 val.className = 'twofa-backup-code-value';
123 val.style.cssText = 'font-size:1.25em;font-weight:600;letter-spacing:0.05em;'; 124 val.textContent = codeText; 125 126 var group = document.createElement('span'); 127 group.className = 'btn-group'; 128 129 var b1 = document.createElement('button'); 130 b1.type = 'button'; 131 b1.className = 'btn btn-default btn-sm twofa-backup-copy'; 132 b1.title = 'Copy'; 133 b1.setAttribute('aria-label', 'Copy'); 134 b1.innerHTML = '<i class="ls ls-copy" aria-hidden="true"></i>'; 135 136 var b2 = document.createElement('button'); 137 b2.type = 'button'; 138 b2.className = 'btn btn-default btn-sm twofa-backup-download'; 139 b2.title = 'Download'; 140 b2.setAttribute('aria-label', 'Download'); 141 b2.innerHTML = '<i class="ls ls-download" aria-hidden="true"></i>'; 142 143 group.appendChild(b1); 144 group.appendChild(b2); 145 inner.appendChild(val); 146 inner.appendChild(group); 147 toolbar.appendChild(inner); 148 149 if (el.nextSibling) { 150 el.parentNode.insertBefore(toolbar, el.nextSibling); 151 } else { 152 el.parentNode.appendChild(toolbar); 153 } 154 } 155 156 function patchUpdateAjaxModal() { 157 if (window.whmcsTwofaBackupUpdateAjaxModalPatched) { 158 return; 159 } 160 if (typeof window.updateAjaxModal !== 'function') { 161 return; 162 } 163 window.whmcsTwofaBackupUpdateAjaxModalPatched = true; 164 var orig = window.updateAjaxModal; 165 window.updateAjaxModal = function (data) { 166 orig(data); 167 enhanceWhmcsModalTwoFactorBackupCode(); 168 }; 169 } 170 171 function init() { 172 whmcsTwofaBackupEnsureDelegation(); 173 patchUpdateAjaxModal(); 174 enhanceWhmcsModalTwoFactorBackupCode(); 175 } 176 177 if (typeof jQuery !== 'undefined') { 178 jQuery(init); 179 } else { 180 if (document.readyState === 'loading') { 181 document.addEventListener('DOMContentLoaded', init); 182 } else { 183 init(); 184 } 185 } 186})();
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.