use super::remote_signer_rpc::SignerRpc;
use super::remote_signer_uri::{SignerConnection, PERMISSIONS};
use super::*;
use tokio::sync::{mpsc, oneshot};
pub(super) struct RemoteSignRequest {
pub(super) request_id: String,
pub(super) unsigned_event_json: String,
}
pub(super) async fn run(
mut rpc: SignerRpc,
relays: Vec<RelayUrl>,
connection: Option<SignerConnection>,
challenge: String,
mut commands: mpsc::Receiver<RemoteSignRequest>,
cancel: oneshot::Receiver<()>,
) {
let result = tokio::select! {
_ = cancel => None,
result = authorize(&mut rpc, &relays, connection, &challenge, &mut commands) => Some(result),
};
if let Some(Err(error)) = result {
let message = if error.to_string().starts_with("Signer declined") {
"Signer declined the request."
} else if error.to_string().starts_with("Sign-in timed out") {
"Sign-in timed out. Try again."
} else {
"Could not connect to the signer. Try again."
};
let _ = rpc.tx.send(CoreMsg::Internal(Box::new(
InternalEvent::RemoteSignerFailed {
token: rpc.token.clone(),
message: message.into(),
},
)));
}
rpc.client.shutdown().await;
}
async fn authorize(
rpc: &mut SignerRpc,
relays: &[RelayUrl],
connection: Option<SignerConnection>,
challenge: &str,
commands: &mut mpsc::Receiver<RemoteSignRequest>,
) -> anyhow::Result<()> {
rpc.subscribe(relays).await?;
if let Some(connection) = connection {
let result = tokio::time::timeout(
Duration::from_secs(90),
rpc.request(
"connect",
vec![
connection.signer.to_hex(),
connection.secret.clone(),
PERMISSIONS.into(),
serde_json::json!({"name":"Iris Chat", "url":"https://iris.to"}).to_string(),
],
),
)
.await
.map_err(|_| anyhow::anyhow!("Sign-in timed out. Try again."))??;
let value = result.as_str().unwrap_or_default();
anyhow::ensure!(
value == "ack" || (!connection.secret.is_empty() && value == connection.secret),
"Invalid signer confirmation."
);
} else {
rpc.progress(crate::RemoteSignerPhase::WaitingForSigner, None);
loop {
let (author, response) = rpc.next_response().await?;
if response.get("error").is_some_and(|error| !error.is_null())
|| response.get("result").and_then(serde_json::Value::as_str) != Some(challenge)
{
continue;
}
rpc.signer = Some(author);
break;
}
}
tokio::time::timeout(
Duration::from_secs(180),
finish_authorization(rpc, commands),
)
.await
.map_err(|_| anyhow::anyhow!("Sign-in timed out. Try again."))?
}
async fn finish_authorization(
rpc: &mut SignerRpc,
commands: &mut mpsc::Receiver<RemoteSignRequest>,
) -> anyhow::Result<()> {
rpc.progress(crate::RemoteSignerPhase::WaitingForApproval, None);
rpc.switch_relays().await?;
let result = rpc.request("get_public_key", Vec::new()).await?;
let owner = PublicKey::from_hex(result.as_str().unwrap_or_default())?;
let _ = rpc.tx.send(CoreMsg::Internal(Box::new(
InternalEvent::RemoteSignerConnected {
token: rpc.token.clone(),
owner_pubkey_hex: owner.to_hex(),
},
)));
let request = commands
.recv()
.await
.ok_or_else(|| anyhow::anyhow!("Sign-in cancelled."))?;
rpc.progress(crate::RemoteSignerPhase::WaitingForApproval, None);
let result = rpc
.request("sign_event", vec![request.unsigned_event_json])
.await?;
let signed_event_json = result
.as_str()
.ok_or_else(|| anyhow::anyhow!("Invalid signer response."))?
.to_string();
let link_info = match tokio::time::timeout(
Duration::from_secs(3),
rpc.request("iris_get_link_info", Vec::new()),
)
.await
{
Ok(Ok(value)) => value
.as_str()
.filter(|value| value.len() <= 4096)
.and_then(|value| {
serde_json::from_str::<super::device_link_signer::DeviceLinkInfo>(value).ok()
})
.filter(|info| info.v == 1 && info.link_id == rpc.keys.public_key().to_hex()),
_ => None,
};
let _ = rpc.tx.send(CoreMsg::Internal(Box::new(
InternalEvent::RemoteSignerSigned {
token: rpc.token.clone(),
request_id: request.request_id,
signed_event_json,
link_info,
},
)));
Ok(())
}