use std::borrow::Cow;
use std::future::IntoFuture;
use std::ops::Deref;
use std::time::Duration;
use async_utility::time;
use nostr::event::{Event, EventId};
use nostr::message::{ClientMessage, MachineReadablePrefix};
use tokio::sync::broadcast;
use crate::error::Error;
use crate::future::BoxedFuture;
use crate::relay::{Relay, RelayNotification};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelaySendEventOutput {
event_id: EventId,
status: EventSendStatus,
}
impl RelaySendEventOutput {
#[inline]
fn new(event_id: EventId, status: EventSendStatus) -> Self {
Self { event_id, status }
}
#[inline]
#[must_use]
pub fn id(&self) -> &EventId {
&self.event_id
}
#[inline]
#[must_use]
pub fn status(&self) -> &EventSendStatus {
&self.status
}
#[inline]
#[must_use]
pub fn into_parts(self) -> (EventId, EventSendStatus) {
(self.event_id, self.status)
}
}
impl Deref for RelaySendEventOutput {
type Target = EventId;
fn deref(&self) -> &Self::Target {
&self.event_id
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EventSendStatus {
Sent,
Ack(EventSendAcknowledgement),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EventSendAcknowledgement {
message: Option<String>,
}
impl EventSendAcknowledgement {
#[inline]
fn new(message: String) -> Self {
Self {
message: if message.is_empty() {
None
} else {
Some(message)
},
}
}
#[inline]
#[must_use]
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
}
impl EventSendStatus {
#[inline]
fn ack(message: String) -> Self {
Self::Ack(EventSendAcknowledgement::new(message))
}
#[inline]
#[must_use]
pub fn is_ack(&self) -> bool {
matches!(self, Self::Ack(..))
}
#[inline]
#[must_use]
pub fn message(&self) -> Option<&str> {
match self {
Self::Ack(ack) => ack.message(),
Self::Sent => None,
}
}
}
#[must_use = "Does nothing unless you await!"]
pub struct SendEvent<'relay, 'event> {
relay: &'relay Relay,
event: &'event Event,
wait_for_ok: bool,
wait_for_ok_timeout: Duration,
wait_for_authentication_timeout: Duration,
}
impl<'relay, 'event> SendEvent<'relay, 'event> {
pub(crate) fn new(relay: &'relay Relay, event: &'event Event) -> Self {
Self {
relay,
event,
wait_for_ok: true,
wait_for_ok_timeout: Duration::from_secs(10),
wait_for_authentication_timeout: Duration::from_secs(10),
}
}
#[inline]
pub fn wait_for_ok(mut self, enable: bool) -> Self {
self.wait_for_ok = enable;
self
}
#[inline]
pub fn ok_timeout(mut self, timeout: Duration) -> Self {
self.wait_for_ok_timeout = timeout;
self
}
#[inline]
pub fn authentication_timeout(mut self, timeout: Duration) -> Self {
self.wait_for_authentication_timeout = timeout;
self
}
async fn send(
&self,
notifications: &mut broadcast::Receiver<RelayNotification>,
event: &Event,
) -> Result<(bool, String), Error> {
self.relay
.send_msg(ClientMessage::Event(Cow::Borrowed(event)))
.await?;
if !self.wait_for_ok {
return Ok((true, String::new()));
}
self.relay
.inner
.wait_for_ok(notifications, &event.id, self.wait_for_ok_timeout)
.await
}
}
async fn wait_for_authentication(
notifications: &mut broadcast::Receiver<RelayNotification>,
timeout: Duration,
) -> Result<(), Error> {
time::timeout(Some(timeout), async {
while let Ok(notification) = notifications.recv().await {
match notification {
RelayNotification::Authenticated => {
return Ok(());
}
RelayNotification::AuthenticationFailed => {
return Err(Error::authentication_msg("authentication failed"));
}
RelayNotification::RelayStatus { status } if status.is_disconnected() => {
return Err(Error::not_connected());
}
_ => (),
}
}
Err(Error::state_msg("premature exit"))
})
.await
.ok_or_else(Error::timeout)?
}
impl<'relay, 'event> IntoFuture for SendEvent<'relay, 'event>
where
'event: 'relay,
{
type Output = Result<RelaySendEventOutput, Error>;
type IntoFuture = BoxedFuture<'relay, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut notifications = self.relay.inner.internal_notification_sender.subscribe();
let (status, message) = self.send(&mut notifications, self.event).await?;
if status {
let status: EventSendStatus = if self.wait_for_ok {
EventSendStatus::ack(message)
} else {
EventSendStatus::Sent
};
return Ok(RelaySendEventOutput::new(self.event.id, status));
}
if let Some(MachineReadablePrefix::AuthRequired) =
MachineReadablePrefix::parse(&message)
{
if self.relay.inner.state.is_authenticator_available() {
wait_for_authentication(
&mut notifications,
self.wait_for_authentication_timeout,
)
.await?;
let (status, message) = self.send(&mut notifications, self.event).await?;
return if status {
Ok(RelaySendEventOutput::new(
self.event.id,
EventSendStatus::ack(message),
))
} else {
Err(Error::relay_msg(message))
};
}
}
Err(Error::relay_msg(message))
})
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use nostr::prelude::*;
use super::*;
use crate::authenticator::SignerAuthenticator;
use crate::local_relay::*;
#[tokio::test]
async fn test_ok_msg() {
let mock = MockRelay::run().await.unwrap();
let url = mock.url().await;
let relay: Relay = Relay::new(url);
relay
.try_connect()
.timeout(Duration::from_secs(3))
.await
.unwrap();
let keys = Keys::generate();
let event = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
let output = relay.send_event(&event).await.unwrap();
assert_eq!(output.id(), &event.id);
assert!(output.status().is_ack());
assert_eq!(output.status().message(), None);
let output = relay.send_event(&event).await.unwrap();
assert_eq!(output.id(), &event.id);
assert!(output.status().is_ack());
assert_eq!(
output.status().message(),
Some("duplicate: already have this event")
);
}
#[tokio::test]
async fn test_without_ok_msg() {
let mock = MockRelay::run().await.unwrap();
let url = mock.url().await;
let relay: Relay = Relay::new(url);
relay
.try_connect()
.timeout(Duration::from_secs(3))
.await
.unwrap();
let keys = Keys::generate();
let event = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
let output = relay.send_event(&event).wait_for_ok(false).await.unwrap();
assert_eq!(output.id(), &event.id);
assert_eq!(output.status(), &EventSendStatus::Sent);
assert_eq!(output.status().message(), None);
}
#[tokio::test]
async fn test_nip42_send_event_without_authenticator() {
let opts = LocalRelayBuilderNip42 {
mode: LocalRelayBuilderNip42Mode::Write,
};
let mock = LocalRelay::builder().nip42(opts).build();
mock.run().await.unwrap();
let url = mock.url().await;
let relay: Relay = Relay::new(url);
relay.connect();
let keys = Keys::generate();
let event = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
let err = relay.send_event(&event).await.unwrap_err();
assert_eq!(
MachineReadablePrefix::parse(&err.to_string()).unwrap(),
MachineReadablePrefix::AuthRequired
);
}
#[tokio::test]
async fn test_nip42_send_event_with_authenticator() {
let opts = LocalRelayBuilderNip42 {
mode: LocalRelayBuilderNip42Mode::Write,
};
let mock = LocalRelay::builder().nip42(opts).build();
mock.run().await.unwrap();
let url = mock.url().await;
let keys = Keys::generate();
let authenticator = SignerAuthenticator::new(keys.clone());
let relay: Relay = Relay::builder(url).authenticator(authenticator).build();
relay.connect();
let event = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
assert!(relay.send_event(&event).await.is_ok());
}
}