Compare commits
5 Commits
gbmfixi1fi
...
issue1fix
| Author | SHA1 | Date | |
|---|---|---|---|
| ff05ffbb5e | |||
| 04068b3e96 | |||
| df59ae8792 | |||
| 526e5a2f63 | |||
| 9145be78a3 |
@@ -33,7 +33,8 @@ jobs:
|
|||||||
build-essential \
|
build-essential \
|
||||||
curl \
|
curl \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
xdg-utils
|
xdg-utils \
|
||||||
|
gnupg2
|
||||||
|
|
||||||
- name: Add Windows Rust target
|
- name: Add Windows Rust target
|
||||||
run: rustup target add x86_64-pc-windows-gnu
|
run: rustup target add x86_64-pc-windows-gnu
|
||||||
@@ -51,17 +52,58 @@ jobs:
|
|||||||
cd src-tauri
|
cd src-tauri
|
||||||
cargo build --release --target x86_64-pc-windows-gnu
|
cargo build --release --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
- name: Make Linux ELF executable
|
||||||
|
run: chmod +x src-tauri/target/release/bytechat-desktop
|
||||||
|
|
||||||
|
# 🔑 Import your private GPG key
|
||||||
|
- name: Import GPG key
|
||||||
|
run: |
|
||||||
|
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||||
|
echo "$GPG_KEY_ID:6:" | gpg --import-ownertrust
|
||||||
|
env:
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
|
||||||
|
|
||||||
|
# 🐧 Sign Linux binaries (.deb, .rpm, ELF)
|
||||||
|
- name: Sign Linux artifacts
|
||||||
|
run: |
|
||||||
|
for f in src-tauri/target/release/bytechat-desktop \
|
||||||
|
src-tauri/target/release/bundle/deb/*.deb \
|
||||||
|
src-tauri/target/release/bundle/rpm/*.rpm; do
|
||||||
|
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||||
|
--pinentry-mode loopback --detach-sign -a "$f"
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
|
# 🪟 Sign Windows exe with GPG
|
||||||
|
- name: Sign Windows exe
|
||||||
|
run: |
|
||||||
|
exe=src-tauri/target/x86_64-pc-windows-gnu/release/bytechat-desktop.exe
|
||||||
|
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||||
|
--pinentry-mode loopback --detach-sign -a "$exe"
|
||||||
|
env:
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
|
# 📦 Upload Linux artifacts + signatures + public key
|
||||||
- name: Upload Linux packages
|
- name: Upload Linux packages
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: tauri-linux
|
name: tauri-linux
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/release/bytechat-desktop
|
src-tauri/target/release/bytechat-desktop
|
||||||
|
src-tauri/target/release/bytechat-desktop.asc
|
||||||
src-tauri/target/release/bundle/deb/*.deb
|
src-tauri/target/release/bundle/deb/*.deb
|
||||||
|
src-tauri/target/release/bundle/deb/*.asc
|
||||||
src-tauri/target/release/bundle/rpm/*.rpm
|
src-tauri/target/release/bundle/rpm/*.rpm
|
||||||
|
src-tauri/target/release/bundle/rpm/*.asc
|
||||||
|
key/bytechat-public.gpg
|
||||||
|
|
||||||
|
# 📦 Upload Windows exe + signature
|
||||||
- name: Upload Windows exe
|
- name: Upload Windows exe
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: tauri-windows-exe
|
name: tauri-windows-exe
|
||||||
path: src-tauri/target/x86_64-pc-windows-gnu/release/bytechat-desktop.exe
|
path: |
|
||||||
|
src-tauri/target/x86_64-pc-windows-gnu/release/bytechat-desktop.exe
|
||||||
|
src-tauri/target/x86_64-pc-windows-gnu/release/bytechat-desktop.exe.asc
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -1 +1,12 @@
|
|||||||
# Tauri Desktop Application
|
# Tauri Desktop Application
|
||||||
|
|
||||||
|
This is the Desktop App for [ByteChat](https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/bytechat) ,it supports Windows and Linux for now. And probably the future
|
||||||
|
|
||||||
|
## Recent Focus:
|
||||||
|
|
||||||
|
- [x] - GPG Key Signage
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
Idk Rust javascript html and css. just look at the repo.
|
||||||
|
and tauri
|
||||||
51
key/bytechat-public.gpg
Normal file
51
key/bytechat-public.gpg
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=4tP+
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
29
src-tauri/Cargo.lock
generated
29
src-tauri/Cargo.lock
generated
@@ -346,6 +346,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-store",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3725,6 +3726,22 @@ dependencies = [
|
|||||||
"zbus",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -3944,9 +3961,21 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.16"
|
version = "0.7.16"
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ tauri = { version = "2", features = [] }
|
|||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default"
|
"opener:default",
|
||||||
|
"store:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ fn disable_dmabuf_if_true() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"title": "ByteChat",
|
"title": "ByteChat",
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 700,
|
"height": 700,
|
||||||
"devtools": false,
|
|
||||||
"zoomHotkeysEnabled": false
|
"zoomHotkeysEnabled": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
619
src/main.js
619
src/main.js
@@ -50,9 +50,62 @@ const MAX_MESSAGE_LENGTH = 4000;
|
|||||||
const MAX_ROOM_ID_LENGTH = 32;
|
const MAX_ROOM_ID_LENGTH = 32;
|
||||||
const MAX_MESSAGES = 512;
|
const MAX_MESSAGES = 512;
|
||||||
|
|
||||||
// In-memory storage for keys (Tauri-compatible)
|
// Enhanced keyStorage with better error handling and forced saves
|
||||||
const keyStorage = {
|
const keyStorage = {
|
||||||
// Store RSA keypair as JWK for persistence
|
// 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) {
|
async storeKeyPair(keyPair) {
|
||||||
try {
|
try {
|
||||||
const publicJWK = await window.crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
const publicJWK = await window.crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
||||||
@@ -64,71 +117,89 @@ const keyStorage = {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isRunningInTauri && tauriStore) {
|
if (isRunningInTauri) {
|
||||||
try {
|
try {
|
||||||
await tauriStore.set('rsa_keypair', keyData);
|
const store = await this.initTauriStore();
|
||||||
await tauriStore.save();
|
if (store) {
|
||||||
console.log('RSA keypair stored in Tauri store');
|
await store.set('rsa_keypair', keyData);
|
||||||
|
console.log('RSA keypair stored in Tauri store');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} catch (storeError) {
|
} catch (storeError) {
|
||||||
console.warn('Failed to use Tauri store, falling back to memory:', 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) {
|
} catch (error) {
|
||||||
console.error('Failed to store keypair:', error);
|
console.error('Failed to store keypair:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Fixed loadKeyPair function with correct extractable settings
|
||||||
async loadKeyPair() {
|
async loadKeyPair() {
|
||||||
try {
|
try {
|
||||||
let keyData;
|
let keyData;
|
||||||
|
|
||||||
if (isRunningInTauri && tauriStore) {
|
if (isRunningInTauri) {
|
||||||
try {
|
try {
|
||||||
keyData = await tauriStore.get('rsa_keypair');
|
const store = await this.initTauriStore();
|
||||||
} catch (storeError) {
|
if (store) {
|
||||||
console.warn('Failed to read from Tauri store:', storeError);
|
keyData = await store.get('rsa_keypair');
|
||||||
keyData = this._memoryKeys;
|
if (keyData) {
|
||||||
|
console.log('RSA keypair loaded from Tauri store');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch (storeError) {
|
||||||
keyData = this._memoryKeys;
|
console.warn('Failed to read from Tauri store:', storeError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!keyData || !keyData.publicKey || !keyData.privateKey) {
|
|
||||||
console.log('No stored keypair found');
|
// Fallback to memory if Tauri store failed
|
||||||
return null;
|
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(
|
if (!keyData || !keyData.publicKey || !keyData.privateKey) {
|
||||||
'jwk',
|
console.log('No stored keypair found');
|
||||||
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);
|
|
||||||
return null;
|
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) {
|
async storeSessionKey(roomId, sessionKey) {
|
||||||
try {
|
try {
|
||||||
const keyData = await window.crypto.subtle.exportKey('raw', sessionKey);
|
const keyData = await window.crypto.subtle.exportKey('raw', sessionKey);
|
||||||
@@ -140,23 +211,39 @@ const keyStorage = {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isRunningInTauri && tauriStore) {
|
let stored = false;
|
||||||
|
|
||||||
|
if (isRunningInTauri) {
|
||||||
try {
|
try {
|
||||||
await tauriStore.set(`session_key_${roomId}`, sessionData);
|
const store = await this.initTauriStore();
|
||||||
await tauriStore.save();
|
if (store) {
|
||||||
console.log('Session key stored for room:', roomId);
|
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) {
|
} catch (storeError) {
|
||||||
console.warn('Failed to store session key in Tauri store:', storeError);
|
console.warn('❌ Failed to store session key in Tauri store:', storeError);
|
||||||
this._sessionKeys = this._sessionKeys || {};
|
|
||||||
this._sessionKeys[roomId] = sessionData;
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!stored) {
|
||||||
|
// Fallback to memory
|
||||||
this._sessionKeys = this._sessionKeys || {};
|
this._sessionKeys = this._sessionKeys || {};
|
||||||
this._sessionKeys[roomId] = sessionData;
|
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) {
|
} 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 {
|
try {
|
||||||
let sessionData;
|
let sessionData;
|
||||||
|
|
||||||
if (isRunningInTauri && tauriStore) {
|
if (isRunningInTauri) {
|
||||||
try {
|
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) {
|
} catch (storeError) {
|
||||||
console.warn('Failed to load session key from Tauri store:', storeError);
|
console.warn('⚠️ Failed to load session key from Tauri store:', storeError);
|
||||||
this._sessionKeys = this._sessionKeys || {};
|
|
||||||
sessionData = this._sessionKeys[roomId];
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Fallback to memory if Tauri store failed
|
||||||
|
if (!sessionData) {
|
||||||
this._sessionKeys = this._sessionKeys || {};
|
this._sessionKeys = this._sessionKeys || {};
|
||||||
sessionData = this._sessionKeys[roomId];
|
sessionData = this._sessionKeys[roomId];
|
||||||
|
if (sessionData) {
|
||||||
|
console.log('📝 Session key loaded from memory for room:', roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionData || !sessionData.key) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,23 +284,130 @@ const keyStorage = {
|
|||||||
'raw',
|
'raw',
|
||||||
keyData,
|
keyData,
|
||||||
{ name: 'AES-GCM' },
|
{ name: 'AES-GCM' },
|
||||||
false,
|
true, // Make extractable so we can re-store if needed
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Session key loaded for room:', roomId);
|
console.log('✅ Session key successfully imported for room:', roomId);
|
||||||
return sessionKey;
|
return sessionKey;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load session key:', error);
|
console.error('❌ Failed to load session key for room', roomId, ':', error);
|
||||||
return null;
|
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,
|
_memoryKeys: null,
|
||||||
_sessionKeys: {}
|
_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
|
// Security utilities
|
||||||
const SecurityUtils = {
|
const SecurityUtils = {
|
||||||
sanitizeInput: function(input) {
|
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() {
|
async function generateSessionKey() {
|
||||||
try {
|
try {
|
||||||
|
console.log("🔧 Generating new session key...");
|
||||||
sessionKey = await window.crypto.subtle.generateKey(
|
sessionKey = await window.crypto.subtle.generateKey(
|
||||||
{ name: "AES-GCM", length: 256 },
|
{ name: "AES-GCM", length: 256 },
|
||||||
true,
|
true, // Make extractable for storage
|
||||||
["encrypt", "decrypt"]
|
["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) {
|
if (currentRoom) {
|
||||||
|
console.log("💾 Storing newly generated session key...");
|
||||||
await keyStorage.storeSessionKey(currentRoom, sessionKey);
|
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;
|
return sessionKey;
|
||||||
} catch (error) {
|
} 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");
|
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) {
|
async function decryptSessionKey(encryptedKey) {
|
||||||
try {
|
try {
|
||||||
console.log("Decrypting session key...");
|
console.log("🔓 Decrypting session key...");
|
||||||
|
|
||||||
const decryptedSessionKey = await unwrapAesKeyWithRsa(keyPair.privateKey, encryptedKey);
|
const decryptedSessionKey = await unwrapAesKeyWithRsa(keyPair.privateKey, encryptedKey);
|
||||||
|
|
||||||
// Store the decrypted session key
|
if (!decryptedSessionKey) {
|
||||||
if (currentRoom) {
|
throw new Error('Failed to unwrap session key - got null/undefined result');
|
||||||
await keyStorage.storeSessionKey(currentRoom, decryptedSessionKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("✅ Session key decrypted successfully");
|
||||||
return decryptedSessionKey;
|
return decryptedSessionKey;
|
||||||
|
|
||||||
} catch (error) {
|
} 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");
|
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) => {
|
socket.on('room_joined', async (data) => {
|
||||||
try {
|
try {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
@@ -632,11 +782,16 @@ function setupSocketListeners() {
|
|||||||
currentDisplayName = data.display_name;
|
currentDisplayName = data.display_name;
|
||||||
roomUsers = data.user_keys || {};
|
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);
|
const storedSessionKey = await keyStorage.loadSessionKey(currentRoom);
|
||||||
if (storedSessionKey && !sessionKey) {
|
|
||||||
|
if (storedSessionKey) {
|
||||||
sessionKey = 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
|
// Switch to chat screen
|
||||||
@@ -652,25 +807,49 @@ function setupSocketListeners() {
|
|||||||
|
|
||||||
// Handle session key based on user status
|
// Handle session key based on user status
|
||||||
if (data.is_first_user) {
|
if (data.is_first_user) {
|
||||||
console.log("First user - generating session key");
|
console.log("👑 First user - generating new session key");
|
||||||
await generateSessionKey();
|
await generateSessionKey();
|
||||||
|
// Key is stored in generateSessionKey()
|
||||||
updateStatus("You're the first user! Session key generated. Others will receive it when they join.", false);
|
updateStatus("You're the first user! Session key generated. Others will receive it when they join.", false);
|
||||||
updateInputState();
|
updateInputState();
|
||||||
} else if (sessionKey) {
|
} 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);
|
updateStatus("Session key available. You can chat.", false);
|
||||||
updateInputState();
|
updateInputState();
|
||||||
} else {
|
} 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);
|
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);
|
const publicKeyString = await exportPublicKey(keyPair.publicKey);
|
||||||
socket.emit('request_session_key', {
|
socket.emit('request_session_key', {
|
||||||
room_id: currentRoom,
|
room_id: currentRoom,
|
||||||
public_key: publicKeyString
|
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();
|
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) {
|
} catch (error) {
|
||||||
console.error("Error processing room_joined:", error);
|
console.error("❌ Error processing room_joined:", error);
|
||||||
updateStatus("Error joining room", true);
|
updateStatus("Error joining room", true);
|
||||||
updateInputState();
|
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) => {
|
socket.on('session_key_received', async (data) => {
|
||||||
try {
|
try {
|
||||||
console.log("session_key_received event fired:", data);
|
console.log("🔑 session_key_received event fired for room:", currentRoom);
|
||||||
|
|
||||||
if (!data || !data.encrypted_key) {
|
if (!data || !data.encrypted_key) {
|
||||||
console.log("Invalid session key data received");
|
console.log("❌ Invalid session key data received");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionKey) {
|
if (!sessionKey) {
|
||||||
try {
|
try {
|
||||||
console.log("Attempting to decrypt received session key");
|
console.log("🔓 Decrypting received session key...");
|
||||||
sessionKey = await decryptSessionKey(data.encrypted_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);
|
updateStatus("Session key received! You can now chat securely.", false);
|
||||||
updateInputState();
|
updateInputState();
|
||||||
addSystemMessage("🔑 Session key received - you can now chat!");
|
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) {
|
} 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);
|
updateStatus("Failed to decrypt session key - please try rejoining", true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Session key already exists, ignoring duplicate");
|
console.log("ℹ️ Session key already exists, ignoring duplicate");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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);
|
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
|
// Periodic connection health check
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (socket && isConnected) {
|
if (socket && isConnected) {
|
||||||
@@ -1217,7 +1443,7 @@ window.SecureChat = {
|
|||||||
hasKeyPair: !!keyPair,
|
hasKeyPair: !!keyPair,
|
||||||
keysReady,
|
keysReady,
|
||||||
isConnected,
|
isConnected,
|
||||||
isTauri
|
isTauri: isRunningInTauri
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1247,6 +1473,67 @@ if (isRunningInTauri) {
|
|||||||
return !!loaded;
|
return !!loaded;
|
||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user