From c47f67b901a7bd690efa85caca7c3fdc06a9cb89 Mon Sep 17 00:00:00 2001 From: despiegk Date: Mon, 21 Apr 2025 13:02:32 +0200 Subject: [PATCH] ... --- implementation_plan_indexeddb.md | 507 +++++++++++++++++++++++++++++++ www/js/index.js | 321 ++++++++++++++----- 2 files changed, 758 insertions(+), 70 deletions(-) create mode 100644 implementation_plan_indexeddb.md diff --git a/implementation_plan_indexeddb.md b/implementation_plan_indexeddb.md new file mode 100644 index 0000000..2100c6f --- /dev/null +++ b/implementation_plan_indexeddb.md @@ -0,0 +1,507 @@ +# 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. \ No newline at end of file diff --git a/www/js/index.js b/www/js/index.js index 12d7b53..6adaf9a 100644 --- a/www/js/index.js +++ b/www/js/index.js @@ -70,34 +70,181 @@ function clearAutoLogout() { } } -// LocalStorage functions for key spaces -const STORAGE_PREFIX = 'crypto_space_'; +// IndexedDB setup and functions +const DB_NAME = 'CryptoSpaceDB'; +const DB_VERSION = 1; +const STORE_NAME = 'keySpaces'; -// Save encrypted space to localStorage -function saveSpaceToStorage(spaceName, encryptedData) { - localStorage.setItem(`${STORAGE_PREFIX}${spaceName}`, encryptedData); +// 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 encrypted space from localStorage -function getSpaceFromStorage(spaceName) { - return localStorage.getItem(`${STORAGE_PREFIX}${spaceName}`); +// Get database connection +function getDB() { + return initDatabase(); } -// 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; +// 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(); + }; + }); } -// Remove space from localStorage -function removeSpaceFromStorage(spaceName) { - localStorage.removeItem(`${STORAGE_PREFIX}${spaceName}`); +// 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) => { + console.error('Error retrieving space:', event.target.error); + 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) => { + 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 @@ -131,7 +278,7 @@ function updateLoginUI() { } // Update the spaces dropdown list -function updateSpacesList() { +async function updateSpacesList() { const spacesList = document.getElementById('space-list'); // Clear existing options @@ -139,16 +286,20 @@ function updateSpacesList() { spacesList.remove(1); } - // Get spaces list - const spaces = listSpacesFromStorage(); - - // Add options for each space - spaces.forEach(spaceName => { - const option = document.createElement('option'); - option.value = spaceName; - option.textContent = spaceName; - spacesList.appendChild(option); - }); + 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 @@ -162,8 +313,11 @@ async function performLogin() { } try { - // Get encrypted space from localStorage - const encryptedSpace = getSpaceFromStorage(spaceName); + // 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; @@ -189,6 +343,7 @@ async function performLogin() { document.getElementById('space-result').textContent = `Error logging in: ${result}`; } } catch (e) { + console.error('Login error:', e); document.getElementById('space-result').textContent = `Error: ${e}`; } } @@ -203,19 +358,23 @@ async function performCreateSpace() { return; } - // Check if space already exists - if (getSpaceFromStorage(spaceName)) { - document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`; - 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; + } + // Create new space const result = create_key_space(spaceName); if (result === 0) { // Encrypt and save the space const encryptedSpace = encrypt_key_space(password); - saveSpaceToStorage(spaceName, encryptedSpace); + await saveSpaceToStorage(spaceName, encryptedSpace); isLoggedIn = true; currentSpace = spaceName; @@ -234,6 +393,7 @@ async function performCreateSpace() { document.getElementById('space-result').textContent = `Error creating space: ${result}`; } } catch (e) { + console.error('Error creating space:', e); document.getElementById('space-result').textContent = `Error: ${e}`; } } @@ -329,7 +489,7 @@ async function performCreateKeypair() { // Display public key displaySelectedKeypairPublicKey(); - // Save the updated space to localStorage + // Save the updated space to IndexedDB saveCurrentSpace(); } else { document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`; @@ -409,8 +569,8 @@ function displaySelectedKeypairPublicKey() { } } -// Save the current space to localStorage -function saveCurrentSpace() { +// Save the current space to IndexedDB +async function saveCurrentSpace() { if (!isLoggedIn || !currentSpace) return; try { @@ -424,30 +584,37 @@ function saveCurrentSpace() { } const encryptedSpace = encrypt_key_space(password); - saveSpaceToStorage(currentSpace, encryptedSpace); + await saveSpaceToStorage(currentSpace, encryptedSpace); } catch (e) { console.error('Error saving space:', e); + alert('Error saving space: ' + e); } } -// Delete a space from localStorage -function deleteSpace(spaceName) { +// Delete a space from IndexedDB +async function deleteSpace(spaceName) { if (!spaceName) return false; - // Check if space exists - if (!getSpaceFromStorage(spaceName)) { + 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; } - - // Remove from localStorage - removeSpaceFromStorage(spaceName); - - // If this was the current space, logout - if (isLoggedIn && currentSpace === spaceName) { - performLogout(); - } - - return true; } async function run() { @@ -460,17 +627,24 @@ async function run() { document.getElementById('login-button').addEventListener('click', performLogin); document.getElementById('create-space-button').addEventListener('click', performCreateSpace); document.getElementById('logout-button').addEventListener('click', performLogout); - document.getElementById('delete-space-button').addEventListener('click', () => { + 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.`)) { - if (deleteSpace(currentSpace)) { - document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`; - } else { - document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`; + document.getElementById('space-result').textContent = 'Deleting...'; + try { + const result = await deleteSpace(currentSpace); + if (result) { + 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', () => { + 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'; @@ -478,11 +652,18 @@ async function run() { } if (confirm(`Are you sure you want to delete the space "${selectedSpace}"? This action cannot be undone.`)) { - if (deleteSpace(selectedSpace)) { - document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`; - updateSpacesList(); - } else { - document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`; + document.getElementById('space-result').textContent = 'Deleting...'; + try { + const result = await deleteSpace(selectedSpace); + if (result) { + document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`; + 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}`; } } });