@@ -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 ( ) ;
consol e. log ( 'RSA keypair stored in Tauri store' ) ;
const store = await this . initTauriStore ( ) ;
if ( store ) {
await stor e . 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 ( ) ;
consol e. log ( 'S ession key stored for room:' , roomId ) ;
const store = await this . initTauriStore ( ) ;
if ( store ) {
await stor e . set ( ` s ession_ 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' } ,
fals e,
tru e, // Make extractable so we can re-store if needed
[ 'encrypt' , 'decrypt' ]
) ;
console . log ( 'Session key load ed for room:' , roomId ) ;
console . log ( '✅ Session key successfully import ed 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 s ession key
if ( currentRoom ) {
await keyStorage . storeSessionKey ( currentRoom , decryptedSessionKey ) ;
if ( ! decryptedS essionKey ) {
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 ( "[r oom_ joined] sessionKey:" , ! ! sessionKey , "is FirstUser:" , data . is _first _user ) ;
console . log ( ` 🏠 R oom joined successfully. S essionKey: ${ ! ! 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 d ecrypt received session key" ) ;
session Key = await decryptSessionKey ( data . encrypted _key ) ;
console . log ( "🔓 D ecrypting received session key... " ) ;
const decrypted Key = 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 ;
}
}
} ;
}