// Import our WebAssembly module import init, { create_key_space, encrypt_key_space, decrypt_key_space, logout, create_keypair, select_keypair, list_keypairs, keypair_pub_key, keypair_sign, keypair_verify, derive_public_key, verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric, generate_symmetric_key, derive_key_from_password, encrypt_symmetric, decrypt_symmetric, encrypt_with_password, decrypt_with_password } from '../../pkg/webassembly.js'; // Helper function to convert ArrayBuffer to hex string function bufferToHex(buffer) { return Array.from(new Uint8Array(buffer)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } // Helper function to convert hex string to Uint8Array function hexToBuffer(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.substr(i, 2), 16); } return bytes; } // Session management let lastActivity = Date.now(); let logoutTimer = null; const AUTO_LOGOUT_TIME = 15 * 60 * 1000; // 15 minutes // Update last activity timestamp function updateActivity() { lastActivity = Date.now(); } // Check for inactivity and logout if needed function checkInactivity() { const inactiveTime = Date.now() - lastActivity; if (inactiveTime > AUTO_LOGOUT_TIME) { performLogout(); alert('You have been logged out due to inactivity.'); } } // Setup auto-logout timer function setupAutoLogout() { logoutTimer = setInterval(checkInactivity, 60000); // Check every minute } // Clear auto-logout timer function clearAutoLogout() { if (logoutTimer) { clearInterval(logoutTimer); logoutTimer = null; } } // IndexedDB setup and functions const DB_NAME = 'CryptoSpaceDB'; const DB_VERSION = 1; const STORE_NAME = 'keySpaces'; // Initialize the database function initDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = (event) => { console.error('Error opening database:', event.target.error); reject('Error opening database: ' + event.target.error); }; request.onsuccess = (event) => { const db = event.target.result; resolve(db); }; request.onupgradeneeded = (event) => { const db = event.target.result; // Create object store for key spaces if it doesn't exist if (!db.objectStoreNames.contains(STORE_NAME)) { const store = db.createObjectStore(STORE_NAME, { keyPath: 'name' }); store.createIndex('name', 'name', { unique: true }); store.createIndex('lastAccessed', 'lastAccessed', { unique: false }); } }; }); } // Get database connection function getDB() { return initDatabase(); } // Save encrypted space to IndexedDB async function saveSpaceToStorage(spaceName, encryptedData) { const db = await getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const space = { name: spaceName, encryptedData: encryptedData, created: new Date(), lastAccessed: new Date() }; const request = store.put(space); request.onsuccess = () => { resolve(); }; request.onerror = (event) => { console.error('Error saving space:', event.target.error); reject('Error saving space: ' + event.target.error); }; transaction.oncomplete = () => { db.close(); }; }); } // Get encrypted space from IndexedDB async function getSpaceFromStorage(spaceName) { try { const db = await getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.get(spaceName); request.onsuccess = (event) => { const space = event.target.result; if (space) { // Update last accessed timestamp updateLastAccessed(spaceName).catch(console.error); resolve(space.encryptedData); } else { resolve(null); } }; request.onerror = (event) => { console.error('Error retrieving space:', event.target.error); reject('Error retrieving space: ' + event.target.error); }; transaction.oncomplete = () => { db.close(); }; }); } catch (error) { console.error('Database error in getSpaceFromStorage:', error); return null; } } // Update last accessed timestamp async function updateLastAccessed(spaceName) { const db = await getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.get(spaceName); request.onsuccess = (event) => { const space = event.target.result; if (space) { space.lastAccessed = new Date(); store.put(space); resolve(); } else { resolve(); } }; transaction.oncomplete = () => { db.close(); }; }); } // List all spaces in IndexedDB async function listSpacesFromStorage() { const db = await getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.openCursor(); const spaces = []; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { spaces.push(cursor.value.name); cursor.continue(); } else { resolve(spaces); } }; request.onerror = (event) => { console.error('Error listing spaces:', event.target.error); reject('Error listing spaces: ' + event.target.error); }; transaction.oncomplete = () => { db.close(); }; }); } // Remove space from IndexedDB async function removeSpaceFromStorage(spaceName) { const db = await getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.delete(spaceName); request.onsuccess = () => { resolve(); }; request.onerror = (event) => { console.error('Error removing space:', event.target.error); reject('Error removing space: ' + event.target.error); }; transaction.oncomplete = () => { db.close(); }; }); } // Session state let isLoggedIn = false; let currentSpace = null; let selectedKeypair = null; // Update UI based on login state async function updateLoginUI() { const loginForm = document.getElementById('login-form'); const logoutForm = document.getElementById('logout-form'); const loginStatus = document.getElementById('login-status'); const currentSpaceName = document.getElementById('current-space-name'); if (isLoggedIn) { loginForm.classList.add('hidden'); logoutForm.classList.remove('hidden'); loginStatus.textContent = 'Status: Logged in'; loginStatus.className = 'status logged-in'; currentSpaceName.textContent = currentSpace; } else { loginForm.classList.remove('hidden'); logoutForm.classList.add('hidden'); loginStatus.textContent = 'Status: Not logged in'; loginStatus.className = 'status logged-out'; currentSpaceName.textContent = ''; } // Update the spaces list try { await updateSpacesList(); } catch (e) { console.error('Error updating spaces list in UI:', e); } } // Update the spaces dropdown list async function updateSpacesList() { const spacesList = document.getElementById('space-list'); // Clear existing options while (spacesList.options.length > 1) { spacesList.remove(1); } try { // Get spaces list const spaces = await listSpacesFromStorage(); // Add options for each space spaces.forEach(spaceName => { const option = document.createElement('option'); option.value = spaceName; option.textContent = spaceName; spacesList.appendChild(option); }); } catch (e) { console.error('Error updating spaces list:', e); } } // Login to a space async function performLogin() { const spaceName = document.getElementById('space-name').value.trim(); const password = document.getElementById('space-password').value; if (!spaceName || !password) { document.getElementById('space-result').textContent = 'Please enter both space name and password'; return; } try { // Show loading state document.getElementById('space-result').textContent = 'Loading...'; // Get encrypted space from IndexedDB const encryptedSpace = await getSpaceFromStorage(spaceName); if (!encryptedSpace) { document.getElementById('space-result').textContent = `Space "${spaceName}" not found`; return; } console.log('Retrieved space from IndexedDB:', { spaceName, encryptedDataLength: encryptedSpace.length }); try { // Decrypt the space - this is a synchronous WebAssembly function const result = decrypt_key_space(encryptedSpace, password); console.log('Decrypt result:', result); if (result === 0) { isLoggedIn = true; currentSpace = spaceName; await updateLoginUI(); updateKeypairsList(); document.getElementById('space-result').textContent = `Successfully logged in to space "${spaceName}"`; // Setup auto-logout updateActivity(); setupAutoLogout(); // Add activity listeners document.addEventListener('click', updateActivity); document.addEventListener('keypress', updateActivity); } else { document.getElementById('space-result').textContent = `Error logging in: ${result}`; } } catch (decryptErr) { console.error('Decryption error:', decryptErr); document.getElementById('space-result').textContent = `Decryption error: ${decryptErr}`; } } catch (e) { console.error('Login error:', e); document.getElementById('space-result').textContent = `Error: ${e}`; } } // Create a new space async function performCreateSpace() { const spaceName = document.getElementById('space-name').value.trim(); const password = document.getElementById('space-password').value; if (!spaceName || !password) { document.getElementById('space-result').textContent = 'Please enter both space name and password'; return; } try { // Show loading state document.getElementById('space-result').textContent = 'Loading...'; // Check if space already exists const existingSpace = await getSpaceFromStorage(spaceName); if (existingSpace) { document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`; return; } try { // Create new space console.log('Creating new space:', spaceName); const result = create_key_space(spaceName); console.log('Create space result:', result); if (result === 0) { try { // Encrypt and save the space console.log('Encrypting space with password'); const encryptedSpace = encrypt_key_space(password); console.log('Encrypted space length:', encryptedSpace.length); // Save to IndexedDB console.log('Saving to IndexedDB'); await saveSpaceToStorage(spaceName, encryptedSpace); console.log('Save completed'); isLoggedIn = true; currentSpace = spaceName; await updateLoginUI(); updateKeypairsList(); document.getElementById('space-result').textContent = `Successfully created space "${spaceName}"`; // Setup auto-logout updateActivity(); setupAutoLogout(); // Add activity listeners document.addEventListener('click', updateActivity); document.addEventListener('keypress', updateActivity); } catch (encryptError) { console.error('Error encrypting or saving space:', encryptError); document.getElementById('space-result').textContent = `Error saving space: ${encryptError}`; } } else { document.getElementById('space-result').textContent = `Error creating space: ${result}`; } } catch (createError) { console.error('Error in WebAssembly create_key_space:', createError); document.getElementById('space-result').textContent = `Error creating key space: ${createError}`; } } catch (e) { console.error('Error checking existing space:', e); document.getElementById('space-result').textContent = `Error: ${e}`; } } // Logout from current space function performLogout() { logout(); isLoggedIn = false; currentSpace = null; selectedKeypair = null; updateLoginUI(); clearKeypairsList(); document.getElementById('space-result').textContent = 'Logged out successfully'; // Clear auto-logout clearAutoLogout(); // Remove activity listeners document.removeEventListener('click', updateActivity); document.removeEventListener('keypress', updateActivity); } // Update the keypairs dropdown list function updateKeypairsList() { const selectKeypair = document.getElementById('select-keypair'); // Clear existing options while (selectKeypair.options.length > 1) { selectKeypair.remove(1); } try { // Get keypairs list const keypairs = list_keypairs(); // Add options for each keypair keypairs.forEach(keypairName => { const option = document.createElement('option'); option.value = keypairName; option.textContent = keypairName; selectKeypair.appendChild(option); }); // If there's a selected keypair, select it in the dropdown if (selectedKeypair) { selectKeypair.value = selectedKeypair; } } catch (e) { console.error('Error updating keypairs list:', e); } } // Clear the keypairs dropdown list function clearKeypairsList() { const selectKeypair = document.getElementById('select-keypair'); // Clear existing options while (selectKeypair.options.length > 1) { selectKeypair.remove(1); } // Clear selected keypair display document.getElementById('selected-pubkey-display').textContent = ''; } // Create a new keypair async function performCreateKeypair() { if (!isLoggedIn) { document.getElementById('keypair-management-result').textContent = 'Please login first'; return; } const keypairName = document.getElementById('keypair-name').value.trim(); if (!keypairName) { document.getElementById('keypair-management-result').textContent = 'Please enter a keypair name'; return; } try { // Create new keypair const result = create_keypair(keypairName); if (result === 0) { document.getElementById('keypair-management-result').textContent = `Successfully created keypair "${keypairName}"`; // Update keypairs list updateKeypairsList(); // Select the new keypair selectedKeypair = keypairName; document.getElementById('select-keypair').value = keypairName; // Display public key displaySelectedKeypairPublicKey(); // Save the updated space to IndexedDB saveCurrentSpace(); } else { document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`; } } catch (e) { document.getElementById('keypair-management-result').textContent = `Error: ${e}`; } } // Select a keypair async function performSelectKeypair() { if (!isLoggedIn) { document.getElementById('keypair-management-result').textContent = 'Please login first'; return; } const keypairName = document.getElementById('select-keypair').value; if (!keypairName) { document.getElementById('keypair-management-result').textContent = 'Please select a keypair'; return; } try { // Select keypair const result = select_keypair(keypairName); if (result === 0) { selectedKeypair = keypairName; document.getElementById('keypair-management-result').textContent = `Selected keypair "${keypairName}"`; // Display public key displaySelectedKeypairPublicKey(); } else { document.getElementById('keypair-management-result').textContent = `Error selecting keypair: ${result}`; } } catch (e) { document.getElementById('keypair-management-result').textContent = `Error: ${e}`; } } // Display the public key of the selected keypair function displaySelectedKeypairPublicKey() { try { const pubKey = keypair_pub_key(); const pubKeyHex = bufferToHex(pubKey); // Create a more user-friendly display with copy button const pubKeyDisplay = document.getElementById('selected-pubkey-display'); pubKeyDisplay.innerHTML = `