PageSourceSearch

https://shop.ilfattoquotidiano.it/wp-content/plugins/wp-webauthn/js/frontend.js?ver=1.2.8

js ilfattoquotidiano.it collected 2026-10-01 08:38:17 UTC 33,118 bytes, 637 lines download raw bytes

1'use strict';
2
3// Send an AJAX request and get the response
4const wwa_ajax = function () {
5    let xmlHttpReq = new XMLHttpRequest();
6    return {
7        /** Send an AJAX GET request and get the response
8         * 
9         * @param {string} url URL
10         * @param {string} data Attached data
11         * @param {object} callback Callback function
12         */
13        get: (url, data = '', callback = () => { }) => {
14            xmlHttpReq.open('GET', url + data, true);
15            xmlHttpReq.send();
16            xmlHttpReq.onreadystatechange = () => {
17                if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
18                    callback(xmlHttpReq.responseText, true);
19                } else if (xmlHttpReq.readyState === 4) {
20                    callback('Network Error.', false);
21                }
22            }
23        },
24        /** Send an AJAX POST request and get the response
25         * 
26         * @param {string} url URL
27         * @param {string} data Attached data
28         * @param {object} callback Callback function
29         */
30        post: (url, data = '', callback = () => { }) => {
31            xmlHttpReq.open('POST', url, true);
32            xmlHttpReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
33            xmlHttpReq.send(data);
34            xmlHttpReq.onreadystatechange = () => {
35                if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
36                    callback(xmlHttpReq.responseText, true);
37                } else if (xmlHttpReq.readyState === 4) {
38                    callback('Network Error.', false);
39                }
40            }
41        }
42    }
43};
44
45/** Operate selected DOMs
46 *
47 * @param {string} selector DOM selector
48 * @param {object} callback Callbck function
49 * @param {string} method Selecte method
50 */
51const wwa_dom = (selector, callback = () => { }, method = 'query') => {
52    let dom_list = [];
53    if (method === 'id') {
54        let dom = document.getElementById(selector);
55        if (dom) {
56            callback(dom);
57        }
58        return;
59    } else if (method === 'class') {
60        dom_list = document.getElementsByClassName(selector);
61    } else if (method === 'tag') {
62        dom_list = document.getElementsByTagName(selector);
63    } else {
64        dom_list = document.querySelectorAll(selector);
65    }
66    for (let dom of dom_list) {
67        callback(dom);
68    }
69    return;
70}
71
72/** Code Base64URL into Base64
73 *
74 * @param {string} input Base64URL coded string
75 */
76function base64url2base64(input) {
77    input = input.replace(/=/g, '').replace(/-/g, '+').replace(/_/g, '/');
78    const pad = input.length % 4;
79    if (pad) {
80        if (pad === 1) {
81            throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
82        }
83        input += new Array(5 - pad).join('=');
84    }
85    return input;
86}
87
88/** Code Uint8Array into Base64 string
89 *
90 * @param {Uint8Array} a The Uint8Array needed to be coded into Base64 string
91 */
92function arrayToBase64String(a) {
93    return btoa(String.fromCharCode(...a));
94}
95
96// Disable all WP-Webauthn buttons
97function wwa_disable_buttons() {
98    wwa_dom('wwa-test-submit', (dom) => { dom.disabled = true }, 'class');
99    wwa_dom('wwa-test-usernameless-submit', (dom) => { dom.disabled = true }, 'class');
100    wwa_dom('wwa-bind-submit', (dom) => { dom.disabled = true }, 'class');
101    wwa_dom('wwa-login-submit', (dom) => { dom.disabled = true }, 'class');
102    wwa_dom('wp-submit', (dom) => { dom.disabled = true }, 'id');
103}
104
105// Enable all WP-Webauthn buttons
106function wwa_enable_buttons() {
107    wwa_dom('wwa-test-submit', (dom) => { dom.disabled = false }, 'class');
108    wwa_dom('wwa-test-usernameless-submit', (dom) => { dom.disabled = false }, 'class');
109    wwa_dom('wwa-bind-submit', (dom) => { dom.disabled = false }, 'class');
110    wwa_dom('wwa-login-submit', (dom) => { dom.disabled = false }, 'class');
111    wwa_dom('wp-submit', (dom) => { dom.disabled = false }, 'id');
112}
113
114document.addEventListener('DOMContentLoaded', () => {
115    wwa_dom('wwa-login-submit', (dom) => { dom.addEventListener('click', wwa_auth, false) }, 'class');
116    // If traditional form exists
117    if (document.getElementsByClassName('wwa-login-form-traditional').length > 0) {
118        wwa_dom('.wwa-login-form-traditional .login-password', (dom) => {
119            let height = dom.clientHeight;
120            wwa_dom('.wwa-login-form-webauthn .wp-webauthn-notice', (ele) => {
121                ele.style.height = height - 40.4 + 'px';
122            });
123        });
124        wwa_dom('wwa-w2t', (dom) => { dom.addEventListener('click', wwa_toggle, false) }, 'class');
125        wwa_dom('wwa-t2w', (dom) => { dom.addEventListener('click', wwa_toggle, false) }, 'class');
126    } else {
127        wwa_dom('.wwa-login-form-webauthn .wp-webauthn-notice', (ele) => {
128            ele.style.height = '40.6px';
129        });
130        wwa_dom('wwa-w2t', (dom) => { dom.parentNode.removeChild(document.getElementsByClassName('wwa-w2t')[0]) }, 'class');
131    }
132    // If not support
133    if (window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function') {
134        wwa_dom('wwa-test-submit', (dom) => { dom.disabled = true }, 'class');
135        wwa_dom('wwa-test-usernameless-submit', (dom) => { dom.disabled = true }, 'class');
136        wwa_dom('wwa-bind-submit', (dom) => { dom.disabled = true }, 'class');
137        wwa_dom('wwa-show-test', (dom) => { dom.innerText = wwa_php_vars.i18n_31 }, 'class');
138        wwa_dom('wwa-show-progress', (dom) => { dom.innerText = wwa_php_vars.i18n_31 }, 'class');
139        if (document.getElementsByClassName('wwa-login-form-traditional').length > 0) {
140            wwa_dom('wwa-login-form-webauthn', (dom) => { dom.classList.add('wwa-hide-form') }, 'class');
141        }
142        return;
143    }
144    wwa_dom('wwa-login-form-traditional', (dom) => { dom.classList.add('wwa-hide-form') }, 'class');
145    wwa_dom('wwa-bind-submit', (dom) => { dom.addEventListener('click', wwa_bind, false) }, 'class');
146    wwa_dom('wwa-test-submit', (dom) => { dom.addEventListener('click', wwa_verify, false) }, 'class');
147    wwa_dom('wwa-test-usernameless-submit', (dom) => { dom.addEventListener('click', wwa_verify, false) }, 'class');
148    updateList();
149});
150
151// Toggle form
152function wwa_toggle(e) {
153    e.preventDefault();
154    if (document.getElementsByClassName('wwa-login-form-traditional').length > 0) {
155        // Disable buttons if it is not shown
156        if (document.getElementsByClassName('wwa-login-form-traditional')[0].className.indexOf('wwa-hide-form') !== -1) {
157            wwa_dom('wp-submit', (dom) => { dom.disabled = false }, 'id');
158            wwa_dom('wwa-login-submit', (dom) => { dom.disabled = true }, 'class');
159            setTimeout(() => {
160                wwa_dom('user_login', (dom) => { dom.focus() }, 'id');
161            }, 0);
162        } else {
163            wwa_dom('wp-submit', (dom) => { dom.disabled = true }, 'id');
164            wwa_dom('wwa-login-submit', (dom) => { dom.disabled = false }, 'class');
165            setTimeout(() => {
166                wwa_dom('wwa-user-name', (dom) => { dom.focus() }, 'id');
167            }, 0);
168        }
169        document.getElementsByClassName('wwa-login-form-traditional')[0].classList.toggle('wwa-hide-form');
170        document.getElementsByClassName('wwa-login-form-webauthn')[0].classList.toggle('wwa-hide-form');
171    }
172}
173
174// Auth
175function wwa_auth() {
176    if (window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function') {
177        alert(wwa_php_vars.i18n_31);
178        return;
179    }
180    
181    let wwa_username = document.getElementById('username').value;
182    if (wwa_username === '' && wwa_php_vars.usernameless !== 'true') {
183        alert(wwa_php_vars.i18n_11);
184        return;
185    }
186    wwa_dom('wwa-user-name', (dom) => { dom.readOnly = true }, 'class');
187    wwa_disable_buttons();
188    let button_dom = this;
189    
190    document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_3;
191    let request = wwa_ajax();
192    request.get(wwa_php_vars.ajax_url, `?action=wwa_auth_start&user=${encodeURIComponent(wwa_username)}&type=auth`, (rawData, status) => {
193        if (status) {
194            document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_4;
195            let data = rawData;
196            try {
197                data = JSON.parse(rawData);
198            } catch (e) {
199                console.warn(rawData);
200                wwa_enable_buttons();
201                if (wwa_php_vars.usernameless === 'true' && wwa_username === '') {
202                    document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7 + wwa_php_vars.i18n_33;
203                } else {
204                    document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7;
205                }
206                wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
207                return;
208            }
209            data.challenge = Uint8Array.from(window.atob(base64url2base64(data.challenge)), (c) => c.charCodeAt(0));
210
211            if (data.allowCredentials) {
212                data.allowCredentials = data.allowCredentials.map((item) => {
213                    item.id = Uint8Array.from(window.atob(base64url2base64(item.id)), (c) => c.charCodeAt(0));
214                    return item;
215                });
216            }
217
218            // Save client ID
219            const clientID = data.clientID;
220            delete data.clientID;
221
222            navigator.credentials.get({ 'publicKey': data }).then((credentialInfo) => {
223                document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_5;
224                return credentialInfo;
225            }).then((data) => {
226                const publicKeyCredential = {
227                    id: data.id,
228                    type: data.type,
229                    rawId: arrayToBase64String(new Uint8Array(data.rawId)),
230                    response: {
231                        authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)),
232                        clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
233                        signature: arrayToBase64String(new Uint8Array(data.response.signature)),
234                        userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null
235                    }
236                };
237                return publicKeyCredential;
238            }).then(JSON.stringify).then((AuthenticatorResponse) => {
239                let response = wwa_ajax();
240                response.post(`${wwa_php_vars.ajax_url}?action=wwa_auth`, `data=${encodeURIComponent(window.btoa(AuthenticatorResponse))}&type=auth&clientid=${clientID}&user=${encodeURIComponent(wwa_username)}&remember=${wwa_php_vars.remember_me === 'false' ? 'false' : (document.getElementById('wwa-rememberme') ? (document.getElementById('wwa-rememberme').checked ? 'true' : 'false') : 'fal
240se')}`, (data, status) => {
241                    if (status) {
242                        if (data === 'true') {
243                            wwa_enable_buttons();
244                            wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
245                            document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_6;
246                            if (document.querySelectorAll('input[name="redirect"]').length > 0) {
247                                setTimeout(() => {
248                                    window.location.href = document.querySelectorAll('input[name="redirect"]')[0].value;
249                                }, 200);
250                            } else {
251                                if (document.getElementsByClassName('wwa-redirect-to').length > 0) {
252                                    setTimeout(() => {
253                                        window.location.href = document.getElementsByClassName('wwa-redirect-to')[0].value;
254                                    }, 200);
255                                } else {
256                                    setTimeout(() => {
257                                        window.location.reload();
258                                    }, 200);
259                                }
260                            }
261                        } else {
262                            wwa_enable_buttons();
263                            if (wwa_php_vars.usernameless === 'true' && wwa_username === '') {
264                                document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7 + wwa_php_vars.i18n_33;
265                            } else {
266                                document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7;
267                            }
268                            wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
269                        }
270                    } else {
271                        wwa_enable_buttons();
272                        if (wwa_php_vars.usernameless === 'true' && wwa_username === '') {
273                            document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7 + wwa_php_vars.i18n_33;
274                        } else {
275                            document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7;
276                        }
277                        wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
278                    }
279                })
280            }).catch((error) => {
281                console.warn(error);
282                wwa_enable_buttons();
283                if (wwa_php_vars.usernameless === 'true' && wwa_username === '') {
284                    document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7 + wwa_php_vars.i18n_33;
285                } else {
286                    document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7;
287                }
288                wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
289            })
290        } else {
291            wwa_enable_buttons();
292            if (wwa_php_vars.usernameless === 'true' && wwa_username === '') {
293                document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7 + wwa_php_vars.i18n_33;
294            } else {
295                document.getElementById('wwa-login-form-webauthn-text').innerHTML = wwa_php_vars.i18n_7;
296            }
297            wwa_dom('wwa-user-name', (dom) => { dom.readOnly = false }, 'class');
298        }
299    })
300}
301
302// Bind
303function wwa_bind() {
304    let button_dom = this;
305    let wwa_name = this.parentNode.parentNode.getElementsByClassName('wwa-authenticator-name')[0].value;
306    if (wwa_name === '') {
307        alert(wwa_php_vars.i18n_12);
308        return;
309    }
310    let wwa_type = this.parentNode.parentNode.getElementsByClassName('wwa-authenticator-type')[0].value;
311    let wwa_usernameless = this.parentNode.parentNode.querySelectorAll('.wwa-authenticator-usernameless:checked')[0] ? this.parentNode.parentNode.querySelectorAll('.wwa-authenticator-usernameless:checked')[0].value : 'false';
312    button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_3;
313    wwa_disable_buttons();
314    // Lock options
315    wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = true }, 'class');
316    wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = true }, 'class');
317    wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = true }, 'class');
318    let request = wwa_ajax();
319    request.get(wwa_php_vars.ajax_url, `?action=wwa_create&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}`, (rawData, status) => {
320        if (status) {
321            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_28;
322            let data = rawData;
323            try {
324                data = JSON.parse(rawData);
325            } catch (e) {
326                console.warn(rawData);
327                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_30;
328                wwa_enable_buttons();
329                wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false }, 'class');
330                wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
331                wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
332                updateList();
333                return;
334            }
335            let challenge = new Uint8Array(32);
336            let user_id = new Uint8Array(32);
337            challenge = Uint8Array.from(window.atob(base64url2base64(data.challenge)), (c) =>
337 c.charCodeAt(0));
338            user_id = Uint8Array.from(window.atob(base64url2base64(data.user.id)), (c) => c.charCodeAt(0));
339
340            let public_key = {
341                challenge: challenge,
342                rp: {
343                    id: data.rp.id,
344                    name: data.rp.name
345                },
346                user: {
347                    id: user_id,
348                    name: data.user.name,
349                    displayName: data.user.displayName
350                },
351                pubKeyCredParams: data.pubKeyCredParams,
352                authenticatorSelection: data.authenticatorSelection,
353                timeout: data.timeout
354            }
355
356            // If some authenticators are already registered, exclude
357            if (data.excludeCredentials) {
358                public_key.excludeCredentials = data.excludeCredentials.map((item) => {
359                    item.id = Uint8Array.from(window.atob(base64url2base64(item.id)), (c) => c.charCodeAt(0));
360                    return item;
361                })
362            }
363
364            // Save client ID
365            const clientID = data.clientID;
366            delete data.clientID;
367
368            // Create, a pop-up window should appear
369            navigator.credentials.create({ 'publicKey': public_key }).then((newCredentialInfo) => {
370                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_32;
371                return newCredentialInfo;
372            }).then((data) => {
373                // Code Uint8Array into string for transmission
374                const publicKeyCredential = {
375                    id: data.id,
376                    type: data.type,
377                    rawId: arrayToBase64String(new Uint8Array(data.rawId)),
378                    response: {
379                        clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
380                        attestationObject: arrayToBase64String(new Uint8Array(data.response.attestationObject))
381                    }
382                };
383                return publicKeyCredential;
384            }).then(JSON.stringify).then((AuthenticatorAttestationResponse) => {
385                let response = wwa_ajax();
386                response.post(`${wwa_php_vars.ajax_url}?action=wwa_create_response`, `data=${encodeURIComponent(window.btoa(AuthenticatorAttestationResponse))}&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}&clientid=${clientID}`, (rawData, status) => {
387                    if (status) {
388                        if (rawData === 'true') {
389                            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_29;
390                            wwa_enable_buttons();
391                            wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false; dom.value = '' }, 'class');
392                            wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
393                            wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
394                            updateList();
395                        } else {
396                            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_30;
397                            wwa_enable_buttons();
398                            wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false }, 'class');
399                            wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
400                            wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
401                            updateList();
402                        }
403                    } else {
404                        button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_30;
405                        wwa_enable_buttons();
406                        wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false }, 'class');
407                        wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
408                        wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
409                        updateList();
410                    }
411                })
412            }).catch((error) => {
413                console.warn(error);
414                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_30;
415                wwa_enable_buttons();
416                wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false }, 'class');
417                wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
418                wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
419                updateList();
420            })
421        } else {
422            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_30;
423            wwa_enable_buttons();
424            wwa_dom('wwa-authenticator-name', (dom) => { dom.readOnly = false }, 'class');
425            wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = false }, 'class');
426            wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = false }, 'class');
427            updateList();
428        }
429    })
430}
431
432// Verify
433function wwa_verify() {
434    let button_dom = this;
435    button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_3;
436    let usernameless = this.className.indexOf('wwa-test-usernameless-submit') === -1 ? false : true;
437    wwa_disable_buttons();
438    let request = wwa_ajax();
439    request.get(wwa_php_vars.ajax_url, `?action=wwa_auth_start&type=test&usernameless=${usernameless ? 'true' : 'false'}`, (rawData, status) => {
440        if (status) {
441            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_4;
442            if (rawData === 'User not inited.') {
443                wwa_enable_buttons();
444                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
445                return;
446            }
447            let data = rawData;
448            try {
449                data = JSON.parse(rawData);
450            } catch (e) {
451                console.warn(rawData);
452                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
453                wwa_enable_buttons();
454                return;
455            }
456            data.challenge = Uint8Array.from(window.atob(base64url2base64(data.challenge)), (c) =>
456 c.charCodeAt(0));
457
458            if (data.allowCredentials) {
459                data.allowCredentials = data.allowCredentials.map((item) => {
460                    item.id = Uint8Array.from(window.atob(base64url2base64(item.id)), (c) => c.charCodeAt(0));
461                    return item;
462                });
463            }
464
465            if (data.allowCredentials && wwa_php_vars.allow_authenticator_type && wwa_php_vars.allow_authenticator_type !== 'none') {
466                for (let credential of data.allowCredentials) {
467                    if (wwa_php_vars.allow_authenticator_type === 'cross-platform') {
468                        credential.transports = ['usb', 'nfc', 'ble'];
469                    } else if (wwa_php_vars.allow_authenticator_type === 'platform') {
470                        credential.transports = ['internal'];
471                    }
472                }
473            }
474
475            // Save client ID
476            const clientID = data.clientID;
477            delete data.clientID;
478
479            navigator.credentials.get({ 'publicKey': data }).then((credentialInfo) => {
480                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_13;
481                return credentialInfo;
482            }).then((data) => {
483                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_14;
484                const publicKeyCredential = {
485                    id: data.id,
486                    type: data.type,
487                    rawId: arrayToBase64String(new Uint8Array(data.rawId)),
488                    response: {
489                        authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)),
490                        clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
491                        signature: arrayToBase64String(new Uint8Array(data.response.signature)),
492                        userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null
493                    }
494                };
495                return publicKeyCredential;
496            }).then(JSON.stringify).then((AuthenticatorResponse) => {
497                let response = wwa_ajax();
498                response.post(`${wwa_php_vars.ajax_url}?action=wwa_auth`, `data=${encodeURIComponent(window.btoa(AuthenticatorResponse))}&type=test&remember=false&clientid=${clientID}`, (rawData, status) => {
499                    if (status) {
500                        if (rawData === 'true') {
501                            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_16;
502                            wwa_enable_buttons();
503                            updateList();
504                        } else {
505                            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
506                            wwa_enable_buttons();
507                        }
508                    } else {
509                        button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
510                        wwa_enable_buttons();
511                    }
512                })
513            }).catch((error) => {
514                console.warn(error);
515                button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
516                wwa_enable_buttons();
517            })
518        } else {
519            button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_15;
520            wwa_enable_buttons();
521        }
522    })
523}
524
525// Update authenticator list
526function updateList() {
527    if (document.getElementsByClassName('wwa-authenticator-list').length === 0) {
528        return;
529    }
530    let request = wwa_ajax();
531    request.get(wwa_php_vars.ajax_url, '?action=wwa_authenticator_list', (rawData, status) => {
532        if (status) {
533            let data = rawData;
534            try {
535                data = JSON.parse(rawData);
536            } catch (e) {
537                console.warn(rawData);
538                wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_17}</td></tr>` }, 'class');
539                return;
540            }
541            if (data.length === 0) {
542                if (wwa_php_vars.usernameless === 'true') {
543                    wwa_dom('.wwa-usernameless-th, .wwa-usernameless-td', (dom) => { dom.style.display = 'table-cell' });
544                } else {
545                    wwa_dom('.wwa-usernameless-th, .wwa-usernameless-td', (dom) => { dom.style.display = 'none' });
546                }
547                wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_23}</td></tr>` }, 'class');
548                wwa_dom('wwa-authenticator-list-usernameless-tip', (dom) => { dom.innerText = '' }, 'class');
549                wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = '' }, 'class');
550                return;
551            }
552            let htmlStr = '';
553            let has_usernameless = false;
554            let has_disabled_type = false;
555            for (let item of data) {
556                let item_type_disabled = false;
557                if (item.usernameless) {
558                    has_usernameless = true;
559                }
560                if (wwa_php_vars.allow_authenticator_type !== 'none') {
561                    if (wwa_php_vars.allow_authenticator_type !== item.type) {
562                        has_disabled_type = true;
563                        item_type_disabled = true;
564                    }
565                }
566                htmlStr += `<tr><td>${item.name}</td><td>${item.type === 'none' ? wwa_php_vars.i18n_24 : (item.type === 'platform' ? wwa_php_vars.i18n_25 : wwa_php_vars.i18n_26)}${item_type_disabled ? wwa_php_vars.i18n_35 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}</td><td class="wwa-key-${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name}')">${wwa_php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name}')">${wwa_php_vars.i18n_27}</a></td></tr>`;
567            }
568            wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = htmlStr }, 'class');
569            if (has_usernameless || wwa_php_vars.usernameless === 'true') {
570                wwa_dom('.wwa-usernameless-th, .wwa-usernameless-td', (dom) => { dom.style.display = 'table-cell' });
571            } else {
572                wwa_dom('.wwa-usernameless-th, .wwa-usernameless-td', (dom) => { dom.style.display = 'none' });
573            }
574            if (has_usernameless && wwa_php_vars.usernameless !== 'true') {
575                wwa_dom('wwa-authenticator-list-usernameless-tip', (dom) => { dom.innerText = wwa_php_vars.i18n_10 }, 'class');
576                wwa_dom('wwa-authenticator-list-usernameless-tip', (dom) => { dom.style.display = 'block'; }, 'class');
577            } else {
578                wwa_dom('wwa-authenticator-list-usernameless-tip', (dom) => { dom.innerText = ''; dom.style.display = 'none' }, 'class');
579            }
580            if (has_disabled_type && wwa_php_vars.allow_authenticator_type !== 'none') {
581                if (wwa_php_vars.allow_authenticator_type === 'platform') {
582                    wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = wwa_php_vars.i18n_36 }, 'class');
583                } else {
584                    wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = wwa_php_vars.i18n_37 }, 'class');
585                }
586                wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.style.display = 'block'; }, 'class');
587            } else {
588                wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = ''; dom.style.display = 'none' }, 'class');
589            }
590        } else {
591            wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_17}</td></tr>` }, 'class');
592        }
593    })
594}
595
596/** Rename an authenticator
597 *
598 * @param {string} id Authenticator ID
599 * @param {string} name Current authenticator name
600 */
601function renameAuthenticator(id, name) {
602    let new_name = prompt(wwa_php_vars.i18n_21, name);
603    if (new_name === '') {
604        alert(wwa_php_vars.i18n_12);
605    } else if (new_name !== null && new_name !== name) {
606        let request = wwa_ajax();
607        wwa_dom(`wwa-key-${id}`, (dom) => { dom.innerText = wwa_php_vars.i18n_22 }, 'class');
608        request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&name=${encodeURIComponent(new_name)}&target=rename`, (data, status) => {
609            if (status) {
610                updateList();
611            } else {
612                alert(`Error: ${data}`);
613                updateList();
614            }
615        })
616    }
617}
618
619/** Remove an authenticator
620 *
621 * @param {string} id Authenticator ID
622 * @param {string} name Authenticator name
623 */
624function removeAuthenticator(id, name) {
625    if (confirm(wwa_php_vars.i18n_18 + name + (document.getElementsByClassName('wwa-authenticator-list')[0].children.length === 1 ? '\n' + wwa_php_vars.i18n_34 : ''))) {
626        wwa_dom(`wwa-key-${id}`, (dom) => { dom.innerText = wwa_php_vars.i18n_19 }, 'class');
627        let request = wwa_ajax();
628        request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&target=remove`, (data, status) => {
629            if (status) {
630                updateList();
631            } else {
632                alert(`Error: ${data}`);
633                updateList();
634            }
635        })
636    }
637}

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.