Compare commits

..

No commits in common. "6573a01d7567b51eb62ef3de4e0a4406d1bac2a5" and "2cf31905b0eaf1cc0bbde3f6e03baf6f42c3c8af" have entirely different histories.

3 changed files with 128 additions and 988 deletions

View File

@ -1,507 +0,0 @@
# Implementation Plan: Migrating from LocalStorage to IndexedDB
## Overview
This document outlines the plan for migrating the WebAssembly crypto example application from using `localStorage` to `IndexedDB` for persisting encrypted key spaces. The primary motivations for this migration are:
1. Transaction capabilities for better data integrity
2. Improved performance for larger data operations
3. More structured approach to data storage
## Current Implementation
The current implementation uses localStorage with the following key functions:
```javascript
// LocalStorage functions for key spaces
const STORAGE_PREFIX = 'crypto_space_';
// Save encrypted space to localStorage
function saveSpaceToStorage(spaceName, encryptedData) {
localStorage.setItem(`${STORAGE_PREFIX}${spaceName}`, encryptedData);
}
// Get encrypted space from localStorage
function getSpaceFromStorage(spaceName) {
return localStorage.getItem(`${STORAGE_PREFIX}${spaceName}`);
}
// List all spaces in localStorage
function listSpacesFromStorage() {
const spaces = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(STORAGE_PREFIX)) {
spaces.push(key.substring(STORAGE_PREFIX.length));
}
}
return spaces;
}
// Remove space from localStorage
function removeSpaceFromStorage(spaceName) {
localStorage.removeItem(`${STORAGE_PREFIX}${spaceName}`);
}
```
## Implementation Plan
### 1. Database Structure
- Create a database named 'CryptoSpaceDB'
- Create an object store named 'keySpaces' with 'name' as the key path
- Add indexes for efficient querying: 'name' (unique) and 'lastAccessed'
```mermaid
erDiagram
KeySpaces {
string name PK
string encryptedData
date created
date lastAccessed
}
```
### 2. Database Initialization
Create a module for initializing and managing the IndexedDB database:
```javascript
// Database constants
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) => {
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();
}
```
### 3. Replace Storage Functions
Replace the localStorage functions with IndexedDB equivalents:
```javascript
// 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) => {
reject('Error saving space: ' + event.target.error);
};
transaction.oncomplete = () => {
db.close();
};
});
}
// Get encrypted space from IndexedDB
async function getSpaceFromStorage(spaceName) {
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) => {
reject('Error retrieving space: ' + event.target.error);
};
transaction.oncomplete = () => {
db.close();
};
});
}
// 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) => {
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) => {
reject('Error removing space: ' + event.target.error);
};
transaction.oncomplete = () => {
db.close();
};
});
}
```
### 4. Update Application Flow
Update the login, logout, and other functions to handle the asynchronous nature of IndexedDB:
```javascript
// 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 {
// Get encrypted space from IndexedDB
const encryptedSpace = await getSpaceFromStorage(spaceName);
if (!encryptedSpace) {
document.getElementById('space-result').textContent = `Space "${spaceName}" not found`;
return;
}
// Decrypt the space
const result = decrypt_key_space(encryptedSpace, password);
if (result === 0) {
isLoggedIn = true;
currentSpace = spaceName;
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 (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 {
// Check if space already exists
const existingSpace = await getSpaceFromStorage(spaceName);
if (existingSpace) {
document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`;
return;
}
// Create new space
const result = create_key_space(spaceName);
if (result === 0) {
// Encrypt and save the space
const encryptedSpace = encrypt_key_space(password);
await saveSpaceToStorage(spaceName, encryptedSpace);
isLoggedIn = true;
currentSpace = spaceName;
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);
} else {
document.getElementById('space-result').textContent = `Error creating space: ${result}`;
}
} catch (e) {
document.getElementById('space-result').textContent = `Error: ${e}`;
}
}
// Delete a space from storage
async function deleteSpace(spaceName) {
if (!spaceName) return false;
try {
// Check if space exists
const existingSpace = await getSpaceFromStorage(spaceName);
if (!existingSpace) {
return false;
}
// Remove from IndexedDB
await removeSpaceFromStorage(spaceName);
// If this was the current space, logout
if (isLoggedIn && currentSpace === spaceName) {
performLogout();
}
return true;
} catch (e) {
console.error('Error deleting space:', e);
return false;
}
}
// 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);
}
}
// Save the current space to storage
async function saveCurrentSpace() {
if (!isLoggedIn || !currentSpace) return;
try {
// Store the password in a session variable when logging in
// and use it here to avoid issues when the password field is cleared
const password = document.getElementById('space-password').value;
if (!password) {
console.error('Password not available for saving space');
alert('Please re-enter your password to save changes');
return;
}
const encryptedSpace = encrypt_key_space(password);
await saveSpaceToStorage(currentSpace, encryptedSpace);
} catch (e) {
console.error('Error saving space:', e);
}
}
```
### 5. Update Event Handlers
Update the event handlers in the `run()` function to handle asynchronous operations:
```javascript
document.getElementById('delete-space-button').addEventListener('click', async () => {
if (confirm(`Are you sure you want to delete the space "${currentSpace}"? This action cannot be undone.`)) {
try {
if (await deleteSpace(currentSpace)) {
document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`;
} else {
document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`;
}
} catch (e) {
document.getElementById('space-result').textContent = `Error: ${e}`;
}
}
});
document.getElementById('delete-selected-space-button').addEventListener('click', async () => {
const selectedSpace = document.getElementById('space-list').value;
if (!selectedSpace) {
document.getElementById('space-result').textContent = 'Please select a space to delete';
return;
}
if (confirm(`Are you sure you want to delete the space "${selectedSpace}"? This action cannot be undone.`)) {
try {
if (await deleteSpace(selectedSpace)) {
document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`;
await updateSpacesList();
} else {
document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`;
}
} catch (e) {
document.getElementById('space-result').textContent = `Error: ${e}`;
}
}
});
```
## Testing Strategy
1. **Unit Tests**:
- Test individual IndexedDB functions
- Verify CRUD operations work correctly
2. **Integration Tests**:
- Test full application flow with IndexedDB
- Verify UI updates correctly
3. **Error Handling Tests**:
- Test database connection errors
- Test transaction rollbacks
4. **Performance Tests**:
- Compare performance with localStorage
- Verify improved performance for larger data sets
## Potential Challenges and Solutions
1. **Browser Compatibility**:
- IndexedDB is supported in all modern browsers, but older browsers might have compatibility issues
- Consider using a feature detection approach before initializing IndexedDB
- Provide a fallback mechanism for browsers that don't support IndexedDB
2. **Transaction Management**:
- Properly manage transactions to maintain data integrity
- Ensure all operations within a transaction are completed or rolled back
- Use appropriate transaction modes ('readonly' or 'readwrite')
3. **Error Handling**:
- Implement comprehensive error handling for all IndexedDB operations
- Provide user-friendly error messages
- Log detailed error information for debugging
4. **Asynchronous Operations**:
- Handle Promise rejections with try/catch blocks
- Provide loading indicators for operations that might take time
- Consider using async/await for cleaner code and better error handling
## Implementation Steps
1. Create the database initialization module
2. Implement the IndexedDB storage functions
3. Update the UI functions to handle asynchronous operations
4. Add comprehensive error handling
5. Test all functionality
6. Deploy the updated application
## Conclusion
Migrating from localStorage to IndexedDB will provide better performance, transaction capabilities, and a more structured approach to data storage. The asynchronous nature of IndexedDB requires updates to the application flow, but the benefits outweigh the implementation effort.

View File

@ -33,107 +33,17 @@ function hexToBuffer(hex) {
return bytes; return bytes;
} }
// IndexedDB setup for Ethereum wallets // LocalStorage functions for Ethereum wallets
const DB_NAME = 'EthWalletDB'; const ETH_WALLET_PREFIX = 'eth_wallet_';
const DB_VERSION = 1;
const STORE_NAME = 'ethWallets';
// Initialize the database // Save Ethereum wallet to localStorage
function initDatabase() { function saveEthWalletToStorage(address, privateKey) {
return new Promise((resolve, reject) => { localStorage.setItem(`${ETH_WALLET_PREFIX}${address}`, privateKey);
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = (event) => {
console.error('Error opening Ethereum wallet 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 Ethereum wallets if it doesn't exist
if (!db.objectStoreNames.contains(STORE_NAME)) {
const store = db.createObjectStore(STORE_NAME, { keyPath: 'address' });
store.createIndex('address', 'address', { unique: true });
}
};
});
} }
// Get database connection // Get Ethereum wallet from localStorage
function getDB() { function getEthWalletFromStorage(address) {
return initDatabase(); return localStorage.getItem(`${ETH_WALLET_PREFIX}${address}`);
}
// Save Ethereum wallet to IndexedDB
async function saveEthWalletToStorage(address, privateKey) {
try {
const db = await getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const wallet = {
address: address,
privateKey: privateKey,
created: new Date()
};
const request = store.put(wallet);
request.onsuccess = () => {
resolve();
};
request.onerror = (event) => {
console.error('Error saving Ethereum wallet:', event.target.error);
reject('Error saving wallet: ' + event.target.error);
};
transaction.oncomplete = () => {
db.close();
};
});
} catch (error) {
console.error('Database error in saveEthWalletToStorage:', error);
}
}
// Get Ethereum wallet from IndexedDB
async function getEthWalletFromStorage(address) {
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(address);
request.onsuccess = (event) => {
const wallet = event.target.result;
if (wallet) {
resolve(wallet.privateKey);
} else {
resolve(null);
}
};
request.onerror = (event) => {
console.error('Error retrieving Ethereum wallet:', event.target.error);
reject('Error retrieving wallet: ' + event.target.error);
};
transaction.oncomplete = () => {
db.close();
};
});
} catch (error) {
console.error('Database error in getEthWalletFromStorage:', error);
return null;
}
} }
// Session state // Session state
@ -237,20 +147,13 @@ async function performCreateEthereumWallet() {
} }
try { try {
// Show loading state
document.getElementById('ethereum-wallet-result').textContent = 'Creating wallet...';
// Create Ethereum wallet // Create Ethereum wallet
console.log('Creating Ethereum wallet from keypair:', selectedKeypair);
const result = create_ethereum_wallet(); const result = create_ethereum_wallet();
console.log('Create Ethereum wallet result:', result);
if (result === 0) { if (result === 0) {
hasEthereumWallet = true; hasEthereumWallet = true;
// Get and display Ethereum address // Get and display Ethereum address
const address = get_ethereum_address(); const address = get_ethereum_address();
console.log('Generated Ethereum address:', address);
document.getElementById('ethereum-address-value').textContent = address; document.getElementById('ethereum-address-value').textContent = address;
// Get and display private key // Get and display private key
@ -260,22 +163,14 @@ async function performCreateEthereumWallet() {
// Show the wallet info // Show the wallet info
document.getElementById('ethereum-wallet-info').classList.remove('hidden'); document.getElementById('ethereum-wallet-info').classList.remove('hidden');
try { // Save the wallet to localStorage
// Save the wallet to IndexedDB saveEthWalletToStorage(address, privateKey);
console.log('Saving wallet to IndexedDB:', address);
await saveEthWalletToStorage(address, privateKey); document.getElementById('ethereum-wallet-result').textContent = 'Successfully created Ethereum wallet';
console.log('Wallet saved successfully');
document.getElementById('ethereum-wallet-result').textContent = 'Successfully created Ethereum wallet';
} catch (saveError) {
console.error('Error saving wallet to IndexedDB:', saveError);
document.getElementById('ethereum-wallet-result').textContent = 'Wallet created but failed to save to storage';
}
} else { } else {
document.getElementById('ethereum-wallet-result').textContent = `Error creating Ethereum wallet: ${result}`; document.getElementById('ethereum-wallet-result').textContent = `Error creating Ethereum wallet: ${result}`;
} }
} catch (e) { } catch (e) {
console.error('Error in performCreateEthereumWallet:', e);
document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`; document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`;
} }
} }
@ -295,20 +190,13 @@ async function performCreateEthereumWalletFromName() {
} }
try { try {
// Show loading state
document.getElementById('ethereum-wallet-result').textContent = 'Creating wallet...';
// Create Ethereum wallet from name // Create Ethereum wallet from name
console.log('Creating Ethereum wallet from name:', name);
const result = create_ethereum_wallet_from_name(name); const result = create_ethereum_wallet_from_name(name);
console.log('Create Ethereum wallet from name result:', result);
if (result === 0) { if (result === 0) {
hasEthereumWallet = true; hasEthereumWallet = true;
// Get and display Ethereum address // Get and display Ethereum address
const address = get_ethereum_address(); const address = get_ethereum_address();
console.log('Generated Ethereum address:', address);
document.getElementById('ethereum-address-value').textContent = address; document.getElementById('ethereum-address-value').textContent = address;
// Get and display private key // Get and display private key
@ -318,22 +206,14 @@ async function performCreateEthereumWalletFromName() {
// Show the wallet info // Show the wallet info
document.getElementById('ethereum-wallet-info').classList.remove('hidden'); document.getElementById('ethereum-wallet-info').classList.remove('hidden');
try { // Save the wallet to localStorage
// Save the wallet to IndexedDB saveEthWalletToStorage(address, privateKey);
console.log('Saving wallet to IndexedDB:', address);
await saveEthWalletToStorage(address, privateKey); document.getElementById('ethereum-wallet-result').textContent = `Successfully created Ethereum wallet from name "${name}"`;
console.log('Wallet saved successfully');
document.getElementById('ethereum-wallet-result').textContent = `Successfully created Ethereum wallet from name "${name}"`;
} catch (saveError) {
console.error('Error saving wallet to IndexedDB:', saveError);
document.getElementById('ethereum-wallet-result').textContent = 'Wallet created but failed to save to storage';
}
} else { } else {
document.getElementById('ethereum-wallet-result').textContent = `Error creating Ethereum wallet: ${result}`; document.getElementById('ethereum-wallet-result').textContent = `Error creating Ethereum wallet: ${result}`;
} }
} catch (e) { } catch (e) {
console.error('Error in performCreateEthereumWalletFromName:', e);
document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`; document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`;
} }
} }
@ -348,20 +228,13 @@ async function performCreateEthereumWalletFromPrivateKey() {
} }
try { try {
// Show loading state
document.getElementById('ethereum-wallet-result').textContent = 'Creating wallet...';
// Create Ethereum wallet from private key // Create Ethereum wallet from private key
console.log('Creating Ethereum wallet from private key');
const result = create_ethereum_wallet_from_private_key(privateKey); const result = create_ethereum_wallet_from_private_key(privateKey);
console.log('Create Ethereum wallet from private key result:', result);
if (result === 0) { if (result === 0) {
hasEthereumWallet = true; hasEthereumWallet = true;
// Get and display Ethereum address // Get and display Ethereum address
const address = get_ethereum_address(); const address = get_ethereum_address();
console.log('Generated Ethereum address:', address);
document.getElementById('ethereum-address-value').textContent = address; document.getElementById('ethereum-address-value').textContent = address;
// Get and display private key // Get and display private key
@ -371,22 +244,14 @@ async function performCreateEthereumWalletFromPrivateKey() {
// Show the wallet info // Show the wallet info
document.getElementById('ethereum-wallet-info').classList.remove('hidden'); document.getElementById('ethereum-wallet-info').classList.remove('hidden');
try { // Save the wallet to localStorage
// Save the wallet to IndexedDB saveEthWalletToStorage(address, displayPrivateKey);
console.log('Saving wallet to IndexedDB:', address);
await saveEthWalletToStorage(address, displayPrivateKey); document.getElementById('ethereum-wallet-result').textContent = 'Successfully imported Ethereum wallet from private key';
console.log('Wallet saved successfully');
document.getElementById('ethereum-wallet-result').textContent = 'Successfully imported Ethereum wallet from private key';
} catch (saveError) {
console.error('Error saving wallet to IndexedDB:', saveError);
document.getElementById('ethereum-wallet-result').textContent = 'Wallet imported but failed to save to storage';
}
} else { } else {
document.getElementById('ethereum-wallet-result').textContent = `Error importing Ethereum wallet: ${result}`; document.getElementById('ethereum-wallet-result').textContent = `Error importing Ethereum wallet: ${result}`;
} }
} catch (e) { } catch (e) {
console.error('Error in performCreateEthereumWalletFromPrivateKey:', e);
document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`; document.getElementById('ethereum-wallet-result').textContent = `Error: ${e}`;
} }
} }

View File

@ -70,186 +70,34 @@ function clearAutoLogout() {
} }
} }
// IndexedDB setup and functions // LocalStorage functions for key spaces
const DB_NAME = 'CryptoSpaceDB'; const STORAGE_PREFIX = 'crypto_space_';
const DB_VERSION = 1;
const STORE_NAME = 'keySpaces';
// Initialize the database // Save encrypted space to localStorage
function initDatabase() { function saveSpaceToStorage(spaceName, encryptedData) {
return new Promise((resolve, reject) => { localStorage.setItem(`${STORAGE_PREFIX}${spaceName}`, encryptedData);
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 // Get encrypted space from localStorage
function getDB() { function getSpaceFromStorage(spaceName) {
return initDatabase(); return localStorage.getItem(`${STORAGE_PREFIX}${spaceName}`);
} }
// Save encrypted space to IndexedDB // List all spaces in localStorage
async function saveSpaceToStorage(spaceName, encryptedData) { function listSpacesFromStorage() {
const db = await getDB(); const spaces = [];
return new Promise((resolve, reject) => { for (let i = 0; i < localStorage.length; i++) {
const transaction = db.transaction([STORE_NAME], 'readwrite'); const key = localStorage.key(i);
const store = transaction.objectStore(STORE_NAME); if (key.startsWith(STORAGE_PREFIX)) {
spaces.push(key.substring(STORAGE_PREFIX.length));
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;
} }
return spaces;
} }
// Update last accessed timestamp // Remove space from localStorage
async function updateLastAccessed(spaceName) { function removeSpaceFromStorage(spaceName) {
const db = await getDB(); localStorage.removeItem(`${STORAGE_PREFIX}${spaceName}`);
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 // Session state
@ -258,7 +106,7 @@ let currentSpace = null;
let selectedKeypair = null; let selectedKeypair = null;
// Update UI based on login state // Update UI based on login state
async function updateLoginUI() { function updateLoginUI() {
const loginForm = document.getElementById('login-form'); const loginForm = document.getElementById('login-form');
const logoutForm = document.getElementById('logout-form'); const logoutForm = document.getElementById('logout-form');
const loginStatus = document.getElementById('login-status'); const loginStatus = document.getElementById('login-status');
@ -279,15 +127,11 @@ async function updateLoginUI() {
} }
// Update the spaces list // Update the spaces list
try { updateSpacesList();
await updateSpacesList();
} catch (e) {
console.error('Error updating spaces list in UI:', e);
}
} }
// Update the spaces dropdown list // Update the spaces dropdown list
async function updateSpacesList() { function updateSpacesList() {
const spacesList = document.getElementById('space-list'); const spacesList = document.getElementById('space-list');
// Clear existing options // Clear existing options
@ -295,20 +139,16 @@ async function updateSpacesList() {
spacesList.remove(1); spacesList.remove(1);
} }
try { // Get spaces list
// Get spaces list const spaces = listSpacesFromStorage();
const spaces = await listSpacesFromStorage();
// Add options for each space
// Add options for each space spaces.forEach(spaceName => {
spaces.forEach(spaceName => { const option = document.createElement('option');
const option = document.createElement('option'); option.value = spaceName;
option.value = spaceName; option.textContent = spaceName;
option.textContent = spaceName; spacesList.appendChild(option);
spacesList.appendChild(option); });
});
} catch (e) {
console.error('Error updating spaces list:', e);
}
} }
// Login to a space // Login to a space
@ -322,46 +162,33 @@ async function performLogin() {
} }
try { try {
// Show loading state // Get encrypted space from localStorage
document.getElementById('space-result').textContent = 'Loading...'; const encryptedSpace = getSpaceFromStorage(spaceName);
// Get encrypted space from IndexedDB
const encryptedSpace = await getSpaceFromStorage(spaceName);
if (!encryptedSpace) { if (!encryptedSpace) {
document.getElementById('space-result').textContent = `Space "${spaceName}" not found`; document.getElementById('space-result').textContent = `Space "${spaceName}" not found`;
return; return;
} }
console.log('Retrieved space from IndexedDB:', { spaceName, encryptedDataLength: encryptedSpace.length }); // Decrypt the space
const result = decrypt_key_space(encryptedSpace, password);
try { if (result === 0) {
// Decrypt the space - this is a synchronous WebAssembly function isLoggedIn = true;
const result = decrypt_key_space(encryptedSpace, password); currentSpace = spaceName;
console.log('Decrypt result:', result); updateLoginUI();
updateKeypairsList();
document.getElementById('space-result').textContent = `Successfully logged in to space "${spaceName}"`;
if (result === 0) { // Setup auto-logout
isLoggedIn = true; updateActivity();
currentSpace = spaceName; setupAutoLogout();
await updateLoginUI();
updateKeypairsList(); // Add activity listeners
document.getElementById('space-result').textContent = `Successfully logged in to space "${spaceName}"`; document.addEventListener('click', updateActivity);
document.addEventListener('keypress', updateActivity);
// Setup auto-logout } else {
updateActivity(); document.getElementById('space-result').textContent = `Error logging in: ${result}`;
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) { } catch (e) {
console.error('Login error:', e);
document.getElementById('space-result').textContent = `Error: ${e}`; document.getElementById('space-result').textContent = `Error: ${e}`;
} }
} }
@ -376,61 +203,37 @@ async function performCreateSpace() {
return; return;
} }
// Check if space already exists
if (getSpaceFromStorage(spaceName)) {
document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`;
return;
}
try { try {
// Show loading state // Create new space
document.getElementById('space-result').textContent = 'Loading...'; const result = create_key_space(spaceName);
if (result === 0) {
// Check if space already exists // Encrypt and save the space
const existingSpace = await getSpaceFromStorage(spaceName); const encryptedSpace = encrypt_key_space(password);
if (existingSpace) { saveSpaceToStorage(spaceName, encryptedSpace);
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) { isLoggedIn = true;
try { currentSpace = spaceName;
// Encrypt and save the space updateLoginUI();
console.log('Encrypting space with password'); updateKeypairsList();
const encryptedSpace = encrypt_key_space(password); document.getElementById('space-result').textContent = `Successfully created space "${spaceName}"`;
console.log('Encrypted space length:', encryptedSpace.length);
// Setup auto-logout
// Save to IndexedDB updateActivity();
console.log('Saving to IndexedDB'); setupAutoLogout();
await saveSpaceToStorage(spaceName, encryptedSpace);
console.log('Save completed'); // Add activity listeners
document.addEventListener('click', updateActivity);
isLoggedIn = true; document.addEventListener('keypress', updateActivity);
currentSpace = spaceName; } else {
await updateLoginUI(); document.getElementById('space-result').textContent = `Error creating space: ${result}`;
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) { } catch (e) {
console.error('Error checking existing space:', e);
document.getElementById('space-result').textContent = `Error: ${e}`; document.getElementById('space-result').textContent = `Error: ${e}`;
} }
} }
@ -526,7 +329,7 @@ async function performCreateKeypair() {
// Display public key // Display public key
displaySelectedKeypairPublicKey(); displaySelectedKeypairPublicKey();
// Save the updated space to IndexedDB // Save the updated space to localStorage
saveCurrentSpace(); saveCurrentSpace();
} else { } else {
document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`; document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`;
@ -606,8 +409,8 @@ function displaySelectedKeypairPublicKey() {
} }
} }
// Save the current space to IndexedDB // Save the current space to localStorage
async function saveCurrentSpace() { function saveCurrentSpace() {
if (!isLoggedIn || !currentSpace) return; if (!isLoggedIn || !currentSpace) return;
try { try {
@ -621,37 +424,30 @@ async function saveCurrentSpace() {
} }
const encryptedSpace = encrypt_key_space(password); const encryptedSpace = encrypt_key_space(password);
await saveSpaceToStorage(currentSpace, encryptedSpace); saveSpaceToStorage(currentSpace, encryptedSpace);
} catch (e) { } catch (e) {
console.error('Error saving space:', e); console.error('Error saving space:', e);
alert('Error saving space: ' + e);
} }
} }
// Delete a space from IndexedDB // Delete a space from localStorage
async function deleteSpace(spaceName) { function deleteSpace(spaceName) {
if (!spaceName) return false; if (!spaceName) return false;
try { // Check if space exists
// Check if space exists if (!getSpaceFromStorage(spaceName)) {
const existingSpace = await getSpaceFromStorage(spaceName);
if (!existingSpace) {
return false;
}
// Remove from IndexedDB
await removeSpaceFromStorage(spaceName);
// If this was the current space, logout
if (isLoggedIn && currentSpace === spaceName) {
performLogout();
}
return true;
} catch (e) {
console.error('Error deleting space:', e);
return false; return false;
} }
// Remove from localStorage
removeSpaceFromStorage(spaceName);
// If this was the current space, logout
if (isLoggedIn && currentSpace === spaceName) {
performLogout();
}
return true;
} }
async function run() { async function run() {
@ -664,24 +460,17 @@ async function run() {
document.getElementById('login-button').addEventListener('click', performLogin); document.getElementById('login-button').addEventListener('click', performLogin);
document.getElementById('create-space-button').addEventListener('click', performCreateSpace); document.getElementById('create-space-button').addEventListener('click', performCreateSpace);
document.getElementById('logout-button').addEventListener('click', performLogout); document.getElementById('logout-button').addEventListener('click', performLogout);
document.getElementById('delete-space-button').addEventListener('click', async () => { document.getElementById('delete-space-button').addEventListener('click', () => {
if (confirm(`Are you sure you want to delete the space "${currentSpace}"? This action cannot be undone.`)) { if (confirm(`Are you sure you want to delete the space "${currentSpace}"? This action cannot be undone.`)) {
document.getElementById('space-result').textContent = 'Deleting...'; if (deleteSpace(currentSpace)) {
try { document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`;
const result = await deleteSpace(currentSpace); } else {
if (result) { document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`;
document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`;
} else {
document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`;
}
} catch (e) {
console.error('Error during space deletion:', e);
document.getElementById('space-result').textContent = `Error: ${e}`;
} }
} }
}); });
document.getElementById('delete-selected-space-button').addEventListener('click', async () => { document.getElementById('delete-selected-space-button').addEventListener('click', () => {
const selectedSpace = document.getElementById('space-list').value; const selectedSpace = document.getElementById('space-list').value;
if (!selectedSpace) { if (!selectedSpace) {
document.getElementById('space-result').textContent = 'Please select a space to delete'; document.getElementById('space-result').textContent = 'Please select a space to delete';
@ -689,18 +478,11 @@ async function run() {
} }
if (confirm(`Are you sure you want to delete the space "${selectedSpace}"? This action cannot be undone.`)) { if (confirm(`Are you sure you want to delete the space "${selectedSpace}"? This action cannot be undone.`)) {
document.getElementById('space-result').textContent = 'Deleting...'; if (deleteSpace(selectedSpace)) {
try { document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`;
const result = await deleteSpace(selectedSpace); updateSpacesList();
if (result) { } else {
document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`; document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`;
await updateSpacesList();
} else {
document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`;
}
} catch (e) {
console.error('Error during space deletion:', e);
document.getElementById('space-result').textContent = `Error: ${e}`;
} }
} }
}); });