diff --git a/src/ipc.rs b/src/ipc.rs index 86244c0..2241dd1 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::sync::Mutex; -use crate::app::App; +use crate::app::{App, Nip46ClientSignerHandle}; use crate::errors::AppError; use crate::feed; use crate::profiles; @@ -337,6 +337,22 @@ fn error_code(err: &AppError) -> String { .unwrap_or_else(|_| "error".to_string()) } +/// Lazily ensure the NIP-46 client signer handle exists and return it. +/// +/// App startup defaults to `signer_mode = Nip46Client` but leaves the handle +/// `None` (only SignerModeSet builds one), so a fresh backend answers +/// nip46_* requests with "not initialized" until the user re-saves the mode. +/// Any nip46_* request initializes the handle when the mode is the NIP-46 +/// client mode, matching what SignerModeSet would do. The guard is dropped +/// before returning so callers can await on the handle without deadlocking. +async fn ensure_nip46_signer(app: &Arc>) -> Option { + let mut guard = app.lock().await; + if guard.nip46_signer.is_none() && matches!(guard.signer_mode, SignerMode::Nip46Client) { + guard.nip46_signer = Some(Arc::new(Nip46ClientSigner::new(app.clone()))); + } + guard.nip46_signer.clone() +} + /// Main request dispatcher. async fn run(app: &Arc>, request: Request) -> Result { match request { @@ -402,15 +418,11 @@ async fn run(app: &Arc>, request: Request) -> Result { - // Take the signer handle under the lock, then drop the guard - // before awaiting: connect() re-locks the App internally (to - // persist the connection and resolve its secret), so holding the - // guard across the await would deadlock. - let signer = { - let guard = app.lock().await; - guard.nip46_signer.clone() - }; - let Some(signer) = signer else { + // Take the signer handle (initializing it if needed), then drop + // the guard before awaiting: connect() re-locks the App + // internally (to persist the connection and resolve its secret), + // so holding the guard across the await would deadlock. + let Some(signer) = ensure_nip46_signer(app).await else { return Err(AppError::config( "NIP-46 signer not initialized. Set signer mode to nip46 first.", )); @@ -422,11 +434,7 @@ async fn run(app: &Arc>, request: Request) -> Result>, request: Request) -> Result { - let guard = app.lock().await; - if let Some(signer) = &guard.nip46_signer { - signer.disconnect().await?; - let status = signer.status().await; - Ok(json!(status)) - } else { - Err(AppError::config("NIP-46 signer not initialized")) - } + let Some(signer) = ensure_nip46_signer(app).await else { + return Err(AppError::config("NIP-46 signer not initialized")); + }; + signer.disconnect().await?; + let status = signer.status().await; + Ok(json!(status)) } Request::Nip46Status => { - let guard = app.lock().await; - if let Some(signer) = &guard.nip46_signer { - let status = signer.status().await; - Ok(json!(status)) - } else { - Ok(json!({ "connected": false, "error": "Not initialized" })) - } + let Some(signer) = ensure_nip46_signer(app).await else { + return Ok(json!({ "connected": false, "error": "Not initialized" })); + }; + let status = signer.status().await; + Ok(json!(status)) } Request::Nip46Approve { id, approved } => { - let guard = app.lock().await; - if let Some(signer) = &guard.nip46_signer { - signer.respond_to_approval(&id, approved).await?; - let status = signer.status().await; - Ok(json!(status)) - } else { - Err(AppError::config("NIP-46 signer not initialized")) - } + let Some(signer) = ensure_nip46_signer(app).await else { + return Err(AppError::config("NIP-46 signer not initialized")); + }; + signer.respond_to_approval(&id, approved).await?; + let status = signer.status().await; + Ok(json!(status)) } // Legacy NIP-46 bunker (server mode)