diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 90f5103..e4f41d9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,6 +27,7 @@ tauri-runtime = [ "dep:tauri-plugin-updater", "dep:tauri-plugin-process", "dep:tauri-plugin-notification", + "dep:keyring", ] [[bin]] @@ -73,7 +74,7 @@ base64 = "0.22" agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] } kill_tree = { version = "0.2", features = ["tokio"] } which = "7" -keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] } +keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"], optional = true } axum = { version = "0.8", features = ["ws"] } tower-http = { version = "0.6", features = ["fs", "cors"] } diff --git a/src-tauri/src/keyring_store.rs b/src-tauri/src/keyring_store.rs index dbdcfa2..1fe1874 100644 --- a/src-tauri/src/keyring_store.rs +++ b/src-tauri/src/keyring_store.rs @@ -1,26 +1,30 @@ -use keyring::Entry; - +#[cfg(feature = "tauri-runtime")] const SERVICE_NAME: &str = "codeg"; fn token_key(account_id: &str) -> String { format!("github-token:{}", account_id) } +// ── Tauri mode: OS keyring ── + +#[cfg(feature = "tauri-runtime")] pub fn set_token(account_id: &str, token: &str) -> Result<(), String> { - let entry = Entry::new(SERVICE_NAME, &token_key(account_id)) + let entry = keyring::Entry::new(SERVICE_NAME, &token_key(account_id)) .map_err(|e| format!("keyring init error: {e}"))?; entry .set_password(token) .map_err(|e| format!("keyring set error: {e}")) } +#[cfg(feature = "tauri-runtime")] pub fn get_token(account_id: &str) -> Option { - let entry = Entry::new(SERVICE_NAME, &token_key(account_id)).ok()?; + let entry = keyring::Entry::new(SERVICE_NAME, &token_key(account_id)).ok()?; entry.get_password().ok() } +#[cfg(feature = "tauri-runtime")] pub fn delete_token(account_id: &str) -> Result<(), String> { - let entry = Entry::new(SERVICE_NAME, &token_key(account_id)) + let entry = keyring::Entry::new(SERVICE_NAME, &token_key(account_id)) .map_err(|e| format!("keyring init error: {e}"))?; match entry.delete_credential() { Ok(()) => Ok(()), @@ -28,3 +32,58 @@ pub fn delete_token(account_id: &str) -> Result<(), String> { Err(e) => Err(format!("keyring delete error: {e}")), } } + +// ── Server mode: file-based token store ── + +#[cfg(not(feature = "tauri-runtime"))] +fn tokens_file_path() -> std::path::PathBuf { + let dir = std::env::var("CODEG_DATA_DIR") + .map(std::path::PathBuf::from) + .unwrap_or_else(|_| { + dirs::data_dir() + .map(|d| d.join("codeg")) + .unwrap_or_else(|| std::path::PathBuf::from(".codeg-data")) + }); + dir.join("tokens.json") +} + +#[cfg(not(feature = "tauri-runtime"))] +fn read_tokens() -> std::collections::HashMap { + let path = tokens_file_path(); + std::fs::read_to_string(&path) + .ok() + .and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or_default() +} + +#[cfg(not(feature = "tauri-runtime"))] +fn write_tokens(tokens: &std::collections::HashMap) -> Result<(), String> { + let path = tokens_file_path(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("failed to create token store directory: {e}"))?; + } + let json = serde_json::to_string_pretty(tokens) + .map_err(|e| format!("failed to serialize tokens: {e}"))?; + std::fs::write(&path, json) + .map_err(|e| format!("failed to write token store: {e}")) +} + +#[cfg(not(feature = "tauri-runtime"))] +pub fn set_token(account_id: &str, token: &str) -> Result<(), String> { + let mut tokens = read_tokens(); + tokens.insert(token_key(account_id), token.to_string()); + write_tokens(&tokens) +} + +#[cfg(not(feature = "tauri-runtime"))] +pub fn get_token(account_id: &str) -> Option { + read_tokens().get(&token_key(account_id)).cloned() +} + +#[cfg(not(feature = "tauri-runtime"))] +pub fn delete_token(account_id: &str) -> Result<(), String> { + let mut tokens = read_tokens(); + tokens.remove(&token_key(account_id)); + write_tokens(&tokens) +}