diff --git a/Cargo.toml b/Cargo.toml index 711ffc2..098a9ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"] diesel = ["dep:diesel"] [dependencies] -chrono = { version = "0.4.34", features = ["clock"], default-features = false } +chrono = { version = "0.4.34", features = ["clock", "serde"], default-features = false } serde = { version = "1.0.197", features = ["derive"] } async-trait = "0.1.77" url = { version = "2.5.0", features = ["serde"] } diff --git a/examples/local_federation/objects/person.rs b/examples/local_federation/objects/person.rs index 0ae402f..076e698 100644 --- a/examples/local_federation/objects/person.rs +++ b/examples/local_federation/objects/person.rs @@ -119,7 +119,7 @@ impl DbUser { if use_queue { queue_activity(&activity, self, recipients, data).await?; } else { - let sends = SendActivityTask::prepare(&activity, self, recipients, data).await?; + let sends = SendActivityTask::prepare(&activity, self, recipients, None, data).await?; for send in sends { send.sign_and_send(data).await?; } diff --git a/src/activity_queue.rs b/src/activity_queue.rs index abb5800..98edbf6 100644 --- a/src/activity_queue.rs +++ b/src/activity_queue.rs @@ -49,7 +49,7 @@ where ActorType: Actor, { let config = &data.config; - let tasks = build_tasks(activity, actor, inboxes, data).await?; + let tasks = build_tasks(activity, actor, inboxes, None, data).await?; for task in tasks { // Don't use the activity queue if this is in debug mode, send and wait directly diff --git a/src/activity_sending.rs b/src/activity_sending.rs index f16ef37..94158a2 100644 --- a/src/activity_sending.rs +++ b/src/activity_sending.rs @@ -19,10 +19,10 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use reqwest_middleware::ClientWithMiddleware; use serde::Serialize; use std::{ - self, fmt::{Debug, Display}, time::{Duration, SystemTime}, }; +use chrono::{DateTime, Utc}; use tracing::debug; use url::Url; @@ -51,10 +51,13 @@ impl SendActivityTask { /// - `inboxes`: List of remote actor inboxes that should receive the activity. Ignores local actor /// inboxes. Should be built by calling [crate::traits::Actor::shared_inbox_or_inbox] /// for each target actor. + /// - `published`: Time when the activity was created, so that recipient can apply activities + /// in correct order. pub async fn prepare( activity: &Activity, actor: &ActorType, inboxes: Vec, + published: Option>, data: &Data, ) -> Result, Error> where @@ -62,7 +65,7 @@ impl SendActivityTask { Datatype: Clone, ActorType: Actor, { - build_tasks(activity, actor, inboxes, data).await + build_tasks(activity, actor, inboxes, published, data).await } /// convert a sendactivitydata to a request, signing and sending it @@ -117,6 +120,7 @@ pub(crate) async fn build_tasks<'a, Activity, Datatype, ActorType>( activity: &'a Activity, actor: &ActorType, inboxes: Vec, + published: Option>, data: &Data, ) -> Result, Error> where @@ -127,7 +131,10 @@ where let config = &data.config; let actor_id = activity.actor(); let activity_id = activity.id(); - let activity_serialized: Bytes = serde_json::to_vec(activity) + let activity_serialized: Bytes = match published { + Some(published) => serde_json::to_vec(&WithPublished::new(activity, published)), + None => serde_json::to_vec(activity) + } .map_err(|e| Error::SerializeOutgoingActivity(e, format!("{:?}", activity)))? .into(); let private_key = get_pkey_cached(data, actor).await?; @@ -210,6 +217,24 @@ pub(crate) fn generate_request_headers(inbox_url: &Url) -> HeaderMap { headers } +/// Wrapper struct that adds `published` field with timestamp to outgoing activities. Important that +/// the timestamp includes milliseconds and timezone. +#[derive(Serialize)] +struct WithPublished { + published: DateTime, + #[serde(flatten)] + inner: T, +} + +impl WithPublished { + pub fn new(inner: T, published: DateTime) -> WithPublished { + Self { + published, + inner, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/traits.rs b/src/traits.rs index 9fdec27..ee680f0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -343,7 +343,7 @@ pub mod tests { error::Error, fetch::object_id::ObjectId, http_signatures::{generate_actor_keypair, Keypair}, - protocol::{public_key::PublicKey, verification::verify_domains_match}, + protocol::{verification::verify_domains_match}, }; use activitystreams_kinds::{activity::FollowType, actor::PersonType}; use once_cell::sync::Lazy;