From 04068b3e96ced8b93d12acb49110900c9f777b30 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 28 Aug 2025 22:19:45 +0200 Subject: [PATCH] Issue 1: Completely Fixed Now. --- src-tauri/Cargo.lock | 29 ++ src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 9 +- src-tauri/src/lib.rs | 1 + src-tauri/src/main.rs | 1 - src-tauri/tauri.conf.json | 1 - src/main.js | 619 ++++++++++++++++++++-------- 7 files changed, 490 insertions(+), 171 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ee05592..b75f933 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -346,6 +346,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-opener", + "tauri-plugin-store", ] [[package]] @@ -3725,6 +3726,22 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-store" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85dd80d60a76ee2c2fdce09e9ef30877b239c2a6bb76e6d7d03708aa5f13a19" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.16", + "tokio", + "tracing", +] + [[package]] name = "tauri-runtime" version = "2.8.0" @@ -3944,9 +3961,21 @@ dependencies = [ "pin-project-lite", "slab", "socket2", + "tokio-macros", "windows-sys 0.59.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "tokio-util" version = "0.7.16" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e2ce236..d6e37a4 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,4 +22,5 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +tauri-plugin-store = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 4cdbf49..ddbfac0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,9 +2,12 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main"], + "windows": [ + "main" + ], "permissions": [ "core:default", - "opener:default" + "opener:default", + "store:default" ] -} +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 69fb74b..f856222 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,6 +1,7 @@ #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_opener::init()) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b0c8100..b281f17 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -11,7 +11,6 @@ fn disable_dmabuf_if_true() { } } } - } fn main() { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e686e07..9fd57f8 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -13,7 +13,6 @@ "title": "ByteChat", "width": 600, "height": 700, - "devtools": false, "zoomHotkeysEnabled": false } ], diff --git a/src/main.js b/src/main.js index 39653ec..7a51276 100644 --- a/src/main.js +++ b/src/main.js @@ -50,9 +50,62 @@ const MAX_MESSAGE_LENGTH = 4000; const MAX_ROOM_ID_LENGTH = 32; const MAX_MESSAGES = 512; -// In-memory storage for keys (Tauri-compatible) +// Enhanced keyStorage with better error handling and forced saves const keyStorage = { // Store RSA keypair as JWK for persistence + async initTauriStore() { + if (!isRunningInTauri) return null; + + try { + // Try modern Tauri store API first + if (window.__TAURI_PLUGIN_STORE__) { + const { Store } = window.__TAURI_PLUGIN_STORE__; + return await Store.load('bytechat-keys.json'); + } + // Try legacy API + else if (window.__TAURI__ && window.__TAURI__.store) { + const { Store } = window.__TAURI__.store; + return await Store.load('bytechat-keys.json'); + } + // Try direct access to store + else if (window.__TAURI__ && window.__TAURI__.invoke) { + // Use invoke API directly + return { + get: async (key) => { + try { + return await window.__TAURI__.invoke('plugin:store|get', { key }); + } catch (e) { + console.warn('Store get failed:', e); + return null; + } + }, + set: async (key, value) => { + try { + await window.__TAURI__.invoke('plugin:store|set', { key, value }); + return true; + } catch (e) { + console.warn('Store set failed:', e); + return false; + } + }, + delete: async (key) => { + try { + await window.__TAURI__.invoke('plugin:store|delete', { key }); + return true; + } catch (e) { + console.warn('Store delete failed:', e); + return false; + } + } + }; + } + } catch (e) { + console.warn('Tauri store initialization failed:', e); + } + return null; + }, + + // Store RSA keypair as JWK for persistence async storeKeyPair(keyPair) { try { const publicJWK = await window.crypto.subtle.exportKey('jwk', keyPair.publicKey); @@ -64,71 +117,89 @@ const keyStorage = { timestamp: Date.now() }; - if (isRunningInTauri && tauriStore) { + if (isRunningInTauri) { try { - await tauriStore.set('rsa_keypair', keyData); - await tauriStore.save(); - console.log('RSA keypair stored in Tauri store'); + const store = await this.initTauriStore(); + if (store) { + await store.set('rsa_keypair', keyData); + console.log('RSA keypair stored in Tauri store'); + return true; + } } catch (storeError) { console.warn('Failed to use Tauri store, falling back to memory:', storeError); - this._memoryKeys = keyData; } - } else { - // Fallback to memory storage - this._memoryKeys = keyData; - console.log('RSA keypair stored in memory (no persistent storage)'); } + + // Fallback to memory storage + this._memoryKeys = keyData; + console.log('RSA keypair stored in memory (no persistent storage)'); + return false; } catch (error) { console.error('Failed to store keypair:', error); + throw error; } }, + // Fixed loadKeyPair function with correct extractable settings async loadKeyPair() { - try { - let keyData; - - if (isRunningInTauri && tauriStore) { - try { - keyData = await tauriStore.get('rsa_keypair'); - } catch (storeError) { - console.warn('Failed to read from Tauri store:', storeError); - keyData = this._memoryKeys; + try { + let keyData; + + if (isRunningInTauri) { + try { + const store = await this.initTauriStore(); + if (store) { + keyData = await store.get('rsa_keypair'); + if (keyData) { + console.log('RSA keypair loaded from Tauri store'); + } } - } else { - keyData = this._memoryKeys; + } catch (storeError) { + console.warn('Failed to read from Tauri store:', storeError); } - - if (!keyData || !keyData.publicKey || !keyData.privateKey) { - console.log('No stored keypair found'); - return null; + } + + // Fallback to memory if Tauri store failed + if (!keyData) { + keyData = this._memoryKeys; + if (keyData) { + console.log('RSA keypair loaded from memory'); } - - // Import keys from JWK - const publicKey = await window.crypto.subtle.importKey( - 'jwk', - keyData.publicKey, - { name: 'RSA-OAEP', hash: 'SHA-256' }, - false, - ['encrypt'] - ); - - const privateKey = await window.crypto.subtle.importKey( - 'jwk', - keyData.privateKey, - { name: 'RSA-OAEP', hash: 'SHA-256' }, - false, - ['decrypt'] - ); - - console.log('RSA keypair loaded from storage'); - return { publicKey, privateKey }; - - } catch (error) { - console.error('Failed to load keypair:', error); + } + + if (!keyData || !keyData.publicKey || !keyData.privateKey) { + console.log('No stored keypair found'); return null; } + + // Import keys from JWK - CRITICAL: Make public key extractable for export + const publicKey = await window.crypto.subtle.importKey( + 'jwk', + keyData.publicKey, + { name: 'RSA-OAEP', hash: 'SHA-256' }, + true, // Make extractable so we can export it later + ['encrypt'] + ); + + const privateKey = await window.crypto.subtle.importKey( + 'jwk', + keyData.privateKey, + { name: 'RSA-OAEP', hash: 'SHA-256' }, + false, // Private key doesn't need to be extractable for our use case + ['decrypt'] + ); + + return { publicKey, privateKey }; + + } catch (error) { + console.error('Failed to load keypair:', error); + return null; + } }, + // Also fix the generateKeyPair function to ensure consistent extractable settings + + async storeSessionKey(roomId, sessionKey) { try { const keyData = await window.crypto.subtle.exportKey('raw', sessionKey); @@ -140,23 +211,39 @@ const keyStorage = { timestamp: Date.now() }; - if (isRunningInTauri && tauriStore) { + let stored = false; + + if (isRunningInTauri) { try { - await tauriStore.set(`session_key_${roomId}`, sessionData); - await tauriStore.save(); - console.log('Session key stored for room:', roomId); + const store = await this.initTauriStore(); + if (store) { + await store.set(`session_key_${roomId}`, sessionData); + console.log('โœ… Session key stored in Tauri store for room:', roomId); + + // Verify storage worked + const verification = await store.get(`session_key_${roomId}`); + if (verification) { + stored = true; + } else { + throw new Error('Session key storage verification failed'); + } + } } catch (storeError) { - console.warn('Failed to store session key in Tauri store:', storeError); - this._sessionKeys = this._sessionKeys || {}; - this._sessionKeys[roomId] = sessionData; + console.warn('โŒ Failed to store session key in Tauri store:', storeError); } - } else { + } + + if (!stored) { + // Fallback to memory this._sessionKeys = this._sessionKeys || {}; this._sessionKeys[roomId] = sessionData; - console.log('Session key stored in memory for room:', roomId); + console.log('๐Ÿ“ Session key stored in memory for room:', roomId); } + + return stored; } catch (error) { - console.error('Failed to store session key:', error); + console.error('โŒ Critical error storing session key:', error); + throw error; } }, @@ -164,21 +251,31 @@ const keyStorage = { try { let sessionData; - if (isRunningInTauri && tauriStore) { + if (isRunningInTauri) { try { - sessionData = await tauriStore.get(`session_key_${roomId}`); + const store = await this.initTauriStore(); + if (store) { + sessionData = await store.get(`session_key_${roomId}`); + if (sessionData) { + console.log('โœ… Session key loaded from Tauri store for room:', roomId); + } + } } catch (storeError) { - console.warn('Failed to load session key from Tauri store:', storeError); - this._sessionKeys = this._sessionKeys || {}; - sessionData = this._sessionKeys[roomId]; + console.warn('โš ๏ธ Failed to load session key from Tauri store:', storeError); } - } else { + } + + // Fallback to memory if Tauri store failed + if (!sessionData) { this._sessionKeys = this._sessionKeys || {}; sessionData = this._sessionKeys[roomId]; + if (sessionData) { + console.log('๐Ÿ“ Session key loaded from memory for room:', roomId); + } } if (!sessionData || !sessionData.key) { - console.log('No stored session key found for room:', roomId); + console.log('โŒ No stored session key found for room:', roomId); return null; } @@ -187,23 +284,130 @@ const keyStorage = { 'raw', keyData, { name: 'AES-GCM' }, - false, + true, // Make extractable so we can re-store if needed ['encrypt', 'decrypt'] ); - console.log('Session key loaded for room:', roomId); + console.log('โœ… Session key successfully imported for room:', roomId); return sessionKey; } catch (error) { - console.error('Failed to load session key:', error); + console.error('โŒ Failed to load session key for room', roomId, ':', error); return null; } }, + async clearSessionKey(roomId) { + try { + if (isRunningInTauri) { + try { + const store = await this.initTauriStore(); + if (store) { + await store.delete(`session_key_${roomId}`); + console.log('๐Ÿ—‘๏ธ Session key cleared from Tauri store for room:', roomId); + } + } catch (storeError) { + console.warn('Failed to clear from Tauri store:', storeError); + } + } + + if (this._sessionKeys && this._sessionKeys[roomId]) { + delete this._sessionKeys[roomId]; + console.log('๐Ÿ—‘๏ธ Session key cleared from memory for room:', roomId); + } + } catch (error) { + console.error('โŒ Failed to clear session key for room', roomId, ':', error); + } + }, + + async forceSave() { + // Modern Tauri stores auto-save, this is mainly for compatibility + return true; + }, + _memoryKeys: null, _sessionKeys: {} }; +async function generateKeyPair() { + try { + console.log('=== Starting key generation process ==='); + updateStatus('Loading or generating encryption keys...', false); + + // Try to load existing keypair first + console.log('Attempting to load stored keypair...'); + const storedKeyPair = await keyStorage.loadKeyPair(); + + if (storedKeyPair) { + keyPair = storedKeyPair; + console.log("โœ… Using stored RSA keypair"); + } else { + console.log("๐Ÿ”ง Generating new RSA keypair..."); + + keyPair = await window.crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256" + }, + true, // Make extractable for storage AND export + ["encrypt", "decrypt"] + ); + + console.log("โœ… New RSA keypair generated"); + + // Store the new keypair + console.log("๐Ÿ’พ Storing new keypair..."); + await keyStorage.storeKeyPair(keyPair); + console.log("โœ… Keypair stored successfully"); + } + + // Validate the keypair + if (!keyPair || !keyPair.publicKey || !keyPair.privateKey) { + throw new Error('Invalid keypair generated/loaded'); + } + + // Test that public key is extractable + try { + await window.crypto.subtle.exportKey("spki", keyPair.publicKey); + console.log("โœ… Public key export test passed"); + } catch (exportError) { + console.error("โŒ Public key is not extractable:", exportError); + throw new Error('Public key must be extractable for protocol to work'); + } + + keysReady = true; + updateStatus("Ready to join a room", false); + console.log("โœ… Key pair ready - can now join rooms"); + console.log('=== Key generation process complete ==='); + + } catch (error) { + console.error("โŒ Failed to generate/load key pair:", error); + updateStatus("Error: Failed to set up encryption keys", true); + + // Try to generate a fallback keypair + try { + console.log("๐Ÿ”„ Attempting fallback key generation..."); + keyPair = await window.crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256" + }, + true, // Must be extractable for export + ["encrypt", "decrypt"] + ); + keysReady = true; + updateStatus("Ready to join a room (temporary keys)", false); + console.log("โœ… Fallback keypair generated successfully"); + } catch (fallbackError) { + console.error("โŒ Fallback key generation also failed:", fallbackError); + throw new SecureChatError("Key generation failed completely", "KEY_GEN_ERROR"); + } + } +} // Security utilities const SecurityUtils = { sanitizeInput: function(input) { @@ -307,94 +511,37 @@ function clearSensitiveData() { } } -async function generateKeyPair() { - try { - console.log('=== Starting key generation process ==='); - updateStatus('Loading or generating encryption keys...', false); - - // Try to load existing keypair first - console.log('Attempting to load stored keypair...'); - const storedKeyPair = await keyStorage.loadKeyPair(); - - if (storedKeyPair) { - keyPair = storedKeyPair; - console.log("โœ… Using stored RSA keypair"); - } else { - console.log("๐Ÿ”ง Generating new RSA keypair..."); - - keyPair = await window.crypto.subtle.generateKey( - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256" - }, - true, // Make extractable for storage - ["encrypt", "decrypt"] - ); - - console.log("โœ… New RSA keypair generated"); - - // Store the new keypair - console.log("๐Ÿ’พ Storing new keypair..."); - await keyStorage.storeKeyPair(keyPair); - console.log("โœ… Keypair stored successfully"); - } - - // Validate the keypair - if (!keyPair || !keyPair.publicKey || !keyPair.privateKey) { - throw new Error('Invalid keypair generated/loaded'); - } - - keysReady = true; - updateStatus("Ready to join a room", false); - console.log("โœ… Key pair ready - can now join rooms"); - console.log('=== Key generation process complete ==='); - - } catch (error) { - console.error("โŒ Failed to generate/load key pair:", error); - updateStatus("Error: Failed to set up encryption keys", true); - - // Try to generate a fallback keypair without storage - try { - console.log("๐Ÿ”„ Attempting fallback key generation..."); - keyPair = await window.crypto.subtle.generateKey( - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256" - }, - false, // Not extractable for fallback - ["encrypt", "decrypt"] - ); - keysReady = true; - updateStatus("Ready to join a room (temporary keys)", false); - console.log("โœ… Fallback keypair generated successfully"); - } catch (fallbackError) { - console.error("โŒ Fallback key generation also failed:", fallbackError); - throw new SecureChatError("Key generation failed completely", "KEY_GEN_ERROR"); - } - } -} + +// Enhanced generateSessionKey with guaranteed storage async function generateSessionKey() { try { + console.log("๐Ÿ”ง Generating new session key..."); sessionKey = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, - true, + true, // Make extractable for storage ["encrypt", "decrypt"] ); - console.log("Session key generated"); - // Store session key for this room + if (!sessionKey) { + throw new Error('Session key generation returned null/undefined'); + } + + console.log("โœ… Session key generated"); + + // CRITICAL: Immediately store the session key if (currentRoom) { + console.log("๐Ÿ’พ Storing newly generated session key..."); await keyStorage.storeSessionKey(currentRoom, sessionKey); + await keyStorage.forceSave(); + console.log("โœ… Session key stored and saved successfully"); + } else { + console.warn("โš ๏ธ No current room - session key not stored"); } return sessionKey; } catch (error) { - console.error("Failed to generate session key:", error); + console.error("โŒ Failed to generate session key:", error); throw new SecureChatError("Session key generation failed", "SESSION_KEY_ERROR"); } } @@ -487,20 +634,22 @@ async function encryptSessionKey(sessionKey, publicKey) { } } +// Enhanced decryptSessionKey function async function decryptSessionKey(encryptedKey) { try { - console.log("Decrypting session key..."); + console.log("๐Ÿ”“ Decrypting session key..."); const decryptedSessionKey = await unwrapAesKeyWithRsa(keyPair.privateKey, encryptedKey); - // Store the decrypted session key - if (currentRoom) { - await keyStorage.storeSessionKey(currentRoom, decryptedSessionKey); + if (!decryptedSessionKey) { + throw new Error('Failed to unwrap session key - got null/undefined result'); } + console.log("โœ… Session key decrypted successfully"); return decryptedSessionKey; + } catch (error) { - console.error("Failed to decrypt session key:", error); + console.error("โŒ Failed to decrypt session key:", error); throw new SecureChatError("Session key decryption failed", "SESSION_KEY_DECRYPT_ERROR"); } } @@ -615,6 +764,7 @@ function setupSocketListeners() { } }); + // Enhanced room_joined handler with better key loading socket.on('room_joined', async (data) => { try { if (data.error) { @@ -632,11 +782,16 @@ function setupSocketListeners() { currentDisplayName = data.display_name; roomUsers = data.user_keys || {}; - // Try to load existing session key for this room + // ENHANCED: Try to load existing session key for this room FIRST + console.log("๐Ÿ” Checking for existing session key for room:", currentRoom); const storedSessionKey = await keyStorage.loadSessionKey(currentRoom); - if (storedSessionKey && !sessionKey) { + + if (storedSessionKey) { sessionKey = storedSessionKey; - console.log("Loaded stored session key for room:", currentRoom); + console.log("โœ… Using stored session key for room:", currentRoom); + } else { + console.log("โŒ No stored session key found for room:", currentRoom); + sessionKey = null; // Explicitly clear } // Switch to chat screen @@ -652,25 +807,49 @@ function setupSocketListeners() { // Handle session key based on user status if (data.is_first_user) { - console.log("First user - generating session key"); + console.log("๐Ÿ‘‘ First user - generating new session key"); await generateSessionKey(); + // Key is stored in generateSessionKey() updateStatus("You're the first user! Session key generated. Others will receive it when they join.", false); updateInputState(); } else if (sessionKey) { - console.log("Using existing/stored session key"); + console.log("โœ… Using existing/stored session key"); updateStatus("Session key available. You can chat.", false); updateInputState(); } else { - console.log("Requesting session key from existing users"); + console.log("โ“ Need session key - requesting from existing users"); updateStatus("Requesting session key from other users...", false); - // Explicitly request session key + // Explicitly request session key with retry logic const publicKeyString = await exportPublicKey(keyPair.publicKey); socket.emit('request_session_key', { room_id: currentRoom, public_key: publicKeyString }); + // Set up retry mechanism for session key request + let retryCount = 0; + const maxRetries = 3; + const retryInterval = setInterval(() => { + if (sessionKey) { + clearInterval(retryInterval); + return; + } + + retryCount++; + if (retryCount <= maxRetries) { + console.log(`๐Ÿ”„ Retrying session key request (${retryCount}/${maxRetries})`); + socket.emit('request_session_key', { + room_id: currentRoom, + public_key: publicKeyString + }); + } else { + clearInterval(retryInterval); + console.log("โŒ Max session key request retries reached"); + updateStatus("Failed to obtain session key. Try leaving and rejoining the room.", true); + } + }, 3000); + updateInputState(); } @@ -683,10 +862,10 @@ function setupSocketListeners() { } } - console.log("[room_joined] sessionKey:", !!sessionKey, "isFirstUser:", data.is_first_user); + console.log(`๐Ÿ  Room joined successfully. SessionKey: ${!!sessionKey}, FirstUser: ${data.is_first_user}`); } catch (error) { - console.error("Error processing room_joined:", error); + console.error("โŒ Error processing room_joined:", error); updateStatus("Error joining room", true); updateInputState(); } @@ -751,34 +930,61 @@ function setupSocketListeners() { } }); - // This is the critical event that was likely not firing properly + // Enhanced session_key_received handler with better storage socket.on('session_key_received', async (data) => { try { - console.log("session_key_received event fired:", data); + console.log("๐Ÿ”‘ session_key_received event fired for room:", currentRoom); if (!data || !data.encrypted_key) { - console.log("Invalid session key data received"); + console.log("โŒ Invalid session key data received"); return; } if (!sessionKey) { try { - console.log("Attempting to decrypt received session key"); - sessionKey = await decryptSessionKey(data.encrypted_key); + console.log("๐Ÿ”“ Decrypting received session key..."); + const decryptedKey = await decryptSessionKey(data.encrypted_key); - console.log("Session key decrypted successfully!"); + if (!decryptedKey) { + throw new Error('Decryption returned null/undefined key'); + } + + // Set the session key + sessionKey = decryptedKey; + + // CRITICAL: Explicitly store the session key immediately + console.log("๐Ÿ’พ Storing decrypted session key..."); + await keyStorage.storeSessionKey(currentRoom, sessionKey); + + // Modern Tauri store auto-saves + await keyStorage.forceSave(); + + console.log("โœ… Session key decrypted, stored, and saved successfully!"); updateStatus("Session key received! You can now chat securely.", false); updateInputState(); addSystemMessage("๐Ÿ”‘ Session key received - you can now chat!"); + + // Verify storage by attempting to reload + if (isRunningInTauri) { + console.log("๐Ÿ” Verifying session key storage..."); + const verificationKey = await keyStorage.loadSessionKey(currentRoom); + if (verificationKey) { + console.log("โœ… Session key storage verification successful"); + } else { + console.error("โŒ Session key storage verification failed"); + updateStatus("Warning: Session key may not be saved", true); + } + } + } catch (error) { - console.error("Failed to decrypt received session key:", error); + console.error("โŒ Failed to process received session key:", error); updateStatus("Failed to decrypt session key - please try rejoining", true); } } else { - console.log("Session key already exists, ignoring duplicate"); + console.log("โ„น๏ธ Session key already exists, ignoring duplicate"); } } catch (error) { - console.error("Failed to process session_key_received:", error); + console.error("โŒ Critical error in session_key_received:", error); updateStatus("Error: Failed to process session key", true); } }); @@ -1182,6 +1388,26 @@ document.addEventListener('DOMContentLoaded', () => { } }); +// Add periodic session key backup for Tauri +if (isRunningInTauri) { + // Save session keys periodically in case of unexpected shutdown + setInterval(async () => { + if (sessionKey && currentRoom) { + try { + await keyStorage.storeSessionKey(currentRoom, sessionKey); + console.log("๐Ÿ”„ Periodic session key backup completed"); + } catch (error) { + console.warn("โš ๏ธ Periodic session key backup failed:", error); + } + } + }, 30000); // Every 30 seconds + + // Note: Modern Tauri store auto-saves on app close + window.addEventListener('beforeunload', async () => { + console.log("๐Ÿ”„ App closing - Tauri store will auto-save"); + }); +} + // Periodic connection health check setInterval(() => { if (socket && isConnected) { @@ -1217,7 +1443,7 @@ window.SecureChat = { hasKeyPair: !!keyPair, keysReady, isConnected, - isTauri + isTauri: isRunningInTauri }) }; @@ -1247,6 +1473,67 @@ if (isRunningInTauri) { return !!loaded; } return false; + }, + // Test session key persistence + testSessionKeyPersistence: async () => { + if (!sessionKey || !currentRoom) { + console.log("โŒ No session key or room to test"); + return false; + } + + console.log("๐Ÿงช Testing session key persistence..."); + + try { + // Store current session key + await keyStorage.storeSessionKey(currentRoom, sessionKey); + await keyStorage.forceSave(); + + // Try to load it back + const loaded = await keyStorage.loadSessionKey(currentRoom); + + if (loaded) { + console.log("โœ… Session key persistence test: SUCCESS"); + return true; + } else { + console.log("โŒ Session key persistence test: FAILED"); + return false; + } + } catch (error) { + console.error("โŒ Session key persistence test error:", error); + return false; + } + }, + + // Force session key request + forceRequestSessionKey: async () => { + if (!currentRoom || !keyPair) { + console.log("โŒ Cannot request session key - missing room or keypair"); + return; + } + + console.log("๐Ÿ”„ Forcing session key request..."); + const publicKeyString = await exportPublicKey(keyPair.publicKey); + socket.emit('request_session_key', { + room_id: currentRoom, + public_key: publicKeyString + }); + }, + + // Manual session key storage + forceStoreSessionKey: async () => { + if (!sessionKey || !currentRoom) { + console.log("โŒ No session key or room to store"); + return false; + } + + try { + await keyStorage.storeSessionKey(currentRoom, sessionKey); + console.log("โœ… Manual session key storage completed"); + return true; + } catch (error) { + console.error("โŒ Manual session key storage failed:", error); + return false; + } } }; }