...
This commit is contained in:
parent
2cf31905b0
commit
c47f67b901
507
implementation_plan_indexeddb.md
Normal file
507
implementation_plan_indexeddb.md
Normal file
@ -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.
|
321
www/js/index.js
321
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}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user