Identity IPC commands + frontend identity management UI
New Tauri commands: list_identities, create_identity, switch_identity, delete_identity, import_identity_key, get_active_identity. Settings UI: Identities section with list (active indicator, switch/delete buttons), Create New Identity lightbox, Import Identity Key lightbox. Export/Import buttons as placeholders for Phase 2/3. Identity switch does hot-swap: tears down old Node, starts new one with all background tasks, swaps AppNode RwLock. Frontend reloads after switch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
75ce965b63
commit
18a40756d8
3 changed files with 250 additions and 1 deletions
|
|
@ -2034,6 +2034,118 @@ fn hex_to_postid(hex_str: &str) -> Result<itsgoin_core::types::PostId, String> {
|
|||
|
||||
// --- App setup ---
|
||||
|
||||
// --- Identity management IPC commands ---
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct IdentityInfoDto {
|
||||
node_id: String,
|
||||
display_name: String,
|
||||
created_at: u64,
|
||||
last_used_at: u64,
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_identities(state: State<'_, AppIdentity>) -> Result<Vec<IdentityInfoDto>, String> {
|
||||
let mgr = state.lock().await;
|
||||
let active_id = mgr.active_id();
|
||||
let identities = mgr.list_identities().map_err(|e| e.to_string())?;
|
||||
Ok(identities.into_iter().map(|i| IdentityInfoDto {
|
||||
is_active: active_id.map_or(false, |a| a == i.node_id),
|
||||
node_id: i.node_id_hex,
|
||||
display_name: i.display_name,
|
||||
created_at: i.created_at,
|
||||
last_used_at: i.last_used_at,
|
||||
}).collect())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_identity(state: State<'_, AppIdentity>, name: String) -> Result<String, String> {
|
||||
let mgr = state.lock().await;
|
||||
let node_id = mgr.create_identity(&name).map_err(|e| e.to_string())?;
|
||||
Ok(hex::encode(node_id))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn switch_identity(
|
||||
node_state: State<'_, AppNode>,
|
||||
id_state: State<'_, AppIdentity>,
|
||||
node_id_hex: String,
|
||||
) -> Result<String, String> {
|
||||
let nid_bytes = hex::decode(&node_id_hex).map_err(|e| e.to_string())?;
|
||||
let nid: NodeId = nid_bytes.try_into().map_err(|_| "Invalid node ID".to_string())?;
|
||||
|
||||
let mut mgr = id_state.lock().await;
|
||||
let new_node = mgr.switch_identity(&nid).await.map_err(|e| e.to_string())?;
|
||||
|
||||
// Start background tasks on the new node
|
||||
new_node.start_accept_loop();
|
||||
new_node.start_pull_cycle(300);
|
||||
new_node.start_diff_cycle(120);
|
||||
new_node.start_rebalance_cycle(600);
|
||||
new_node.start_growth_loop();
|
||||
new_node.start_recovery_loop();
|
||||
new_node.start_social_checkin_cycle(3600);
|
||||
new_node.start_anchor_register_cycle(600);
|
||||
new_node.start_upnp_renewal_cycle();
|
||||
new_node.start_upnp_tcp_renewal_cycle();
|
||||
new_node.start_http_server();
|
||||
new_node.start_bootstrap_connectivity_check();
|
||||
new_node.start_replication_cycle(600);
|
||||
|
||||
let cache_max_bytes: u64 = {
|
||||
let storage = new_node.storage.get().await;
|
||||
storage.get_setting("cache_size_bytes")
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1_073_741_824u64)
|
||||
};
|
||||
Node::start_eviction_cycle(Arc::clone(&new_node), 300, cache_max_bytes);
|
||||
|
||||
// Hot-swap the active node
|
||||
{
|
||||
let mut current = node_state.write().await;
|
||||
*current = new_node;
|
||||
}
|
||||
|
||||
Ok(format!("Switched to {}", &node_id_hex[..12]))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn delete_identity(state: State<'_, AppIdentity>, node_id_hex: String) -> Result<String, String> {
|
||||
let nid_bytes = hex::decode(&node_id_hex).map_err(|e| e.to_string())?;
|
||||
let nid: NodeId = nid_bytes.try_into().map_err(|_| "Invalid node ID".to_string())?;
|
||||
let mgr = state.lock().await;
|
||||
mgr.delete_identity(&nid).map_err(|e| e.to_string())?;
|
||||
Ok("Identity deleted".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn import_identity_key(state: State<'_, AppIdentity>, key_hex: String, name: String) -> Result<String, String> {
|
||||
let mgr = state.lock().await;
|
||||
let node_id = mgr.import_identity_from_key(&key_hex, &name).map_err(|e| e.to_string())?;
|
||||
Ok(hex::encode(node_id))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_active_identity(state: State<'_, AppIdentity>) -> Result<Option<IdentityInfoDto>, String> {
|
||||
let mgr = state.lock().await;
|
||||
let active_id = mgr.active_id();
|
||||
if active_id.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
let identities = mgr.list_identities().map_err(|e| e.to_string())?;
|
||||
Ok(identities.into_iter().find(|i| Some(i.node_id) == active_id).map(|i| IdentityInfoDto {
|
||||
is_active: true,
|
||||
node_id: i.node_id_hex,
|
||||
display_name: i.display_name,
|
||||
created_at: i.created_at,
|
||||
last_used_at: i.last_used_at,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tracing_subscriber::fmt()
|
||||
|
|
@ -2216,6 +2328,12 @@ pub fn run() {
|
|||
get_badge_counts,
|
||||
get_last_read_message,
|
||||
generate_share_link,
|
||||
list_identities,
|
||||
create_identity,
|
||||
switch_identity,
|
||||
delete_identity,
|
||||
import_identity_key,
|
||||
get_active_identity,
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue