function base32_nopad_encode(slice) { const encoder = new base32.Encoder({ type: "rfc4648", lc: true }); const code = encoder.write(slice).finalize(); return code.replaceAll('=', ''); } function base64_decode(str_b64) { const raw_str = atob(str_b64); const length = raw_str.length; var b = []; for (var i = 0; i < length; i++) { b.push(raw_str.charCodeAt(i)); } return b; } class Account { constructor(local_part, separator, domain, key) { this.local_part = local_part; this.domain = domain; this.separator = separator; if (Array.isArray(key)) { this.key = key; } else { this.key = base64_decode(key); } } getName() { return `${this.local_part}@${this.domain}`; } genSubAddr(sub_addr_name) { var hasher = sha256.hmac.create(this.key); hasher.update(this.local_part); hasher.update(this.separator); hasher.update(sub_addr_name); const hash = hasher.array(); const offset = hash[hash.length - 1] & 0xf; const reduced_hash = hash.slice(offset, offset + 5); const code = base32_nopad_encode(reduced_hash); return `${this.local_part}${this.separator}${sub_addr_name}${this.separator}${code}@${this.domain}` } register() { localStorage.setItem(this.getName(), JSON.stringify(this)); } } document.addEventListener('DOMContentLoaded', () => { // Functions to open and close a modal function openModal(el) { if (el.id === 'modal-del-account') { document.querySelector('#del-account-name').innerHTML = getSelectedAccountName(); } el.classList.add('is-active'); } function openNewAccountModal(el) { const new_account_modal = document.querySelector('#modal-add-account'); openModal(new_account_modal); } function closeModal(el) { if (!(el.id === 'modal-add-account' && localStorage.length === 0)) { el.classList.remove('is-active'); } } function closeAllModals() { (document.querySelectorAll('.modal') || []).forEach((modal) => { closeModal(modal); }); } // Function to get the name of the currently selected account function getSelectedAccountName() { return document.querySelector('#account-name').value; } // Function to get an account by its name function getAccountByName(name) { const account_string = localStorage.getItem(name); const account_raw = JSON.parse(account_string); return new Account( account_raw.local_part, account_raw.separator, account_raw.domain, account_raw.key, ); } // Function to synchronize the account list function syncAccountList() { var acc_list = document.querySelector('#account-name'); while (acc_list.lastElementChild) { acc_list.removeChild(acc_list.lastElementChild); } var account_names = []; for (var i = 0, len = localStorage.length; i < len; ++i) { account_names.push(localStorage.key(i)); } account_names.sort(); for (const name of account_names) { const account = getAccountByName(name); const new_elem = new Option(account.getName(), account.getName()); acc_list.appendChild(new_elem); } if (localStorage.length === 0) { openNewAccountModal(); } } syncAccountList(); // Add a click event on buttons to open a specific modal (document.querySelectorAll('.js-modal-trigger') || []).forEach((trigger) => { const modal = trigger.dataset.target; const target = document.getElementById(modal); trigger.addEventListener('click', () => { openModal(target); }); }); // Add a click event on various child elements to close the parent modal (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button-close') || []).forEach((close) => { const target = close.closest('.modal'); close.addEventListener('click', () => { closeModal(target); }); }); // Add a keyboard event to close all modals document.addEventListener('keydown', (event) => { if (event.code === 'Escape') { closeAllModals(); } }); // Add a click event on the delete account button to display the confirmation message document.querySelector('#btn-del-account-confirm').addEventListener('click', (event) => { const account_name = getSelectedAccountName(); localStorage.removeItem(account_name); console.log(`Account ${account_name} deleted.`); syncAccountList(); closeAllModals(); }); // Add a click event on the new account button to register the new account document.querySelector('#btn-new-account').addEventListener('click', (event) => { console.log('Adding new account…'); const new_account = new Account( document.querySelector('#new-addr-local-part').value, document.querySelector('#new-addr-separator').value, document.querySelector('#new-addr-domain').value, document.querySelector('#new-addr-key').value, ); console.log(new_account); new_account.register(); console.log(`Account ${new_account.getName()} added.`); ['#new-addr-local-part', '#new-addr-domain', '#new-addr-key'].forEach((selector) => { document.querySelector(selector).value = ''; }); document.querySelector('#new-addr-separator').value = '+'; syncAccountList(); closeAllModals(); }); // Add a click event on the new address button to generate it document.querySelector('#btn-generate').addEventListener('click', (event) => { event.preventDefault(); const account = getAccountByName(document.querySelector('#account-name').value); const new_address = account.genSubAddr(document.querySelector('#sub-addr-name').value); document.querySelector('#generated-addr').value = new_address; console.log(`New sub-address for account ${account.getName()}: ${new_address}`); }); // Add a change event on the main form to remove previously generated address ['#account-name', '#sub-addr-name'].forEach((selector) => { document.querySelector(selector).addEventListener('change', () => { document.querySelector('#generated-addr').value = ''; }); }); }); window.addEventListener('load', () => { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } });