use futures::future::join_all; use itertools::Itertools as _; use serde::Serialize; use web_push::{ ContentEncoding, IsahcWebPushClient, PartialVapidSignatureBuilder, SubscriptionInfo, WebPushClient, WebPushError, WebPushMessage, WebPushMessageBuilder, }; use crate::error::failed::{Failed, ResultExt as _}; pub trait Publish { fn publish( &self, message: M, signer: PartialVapidSignatureBuilder, subscriptions: impl IntoIterator + Send, ) -> impl Future, Failed>> + Send where M: Serialize + Send + 'static; } #[derive(Clone)] pub struct Publisher { client: IsahcWebPushClient, } impl Publisher { pub fn new() -> Result { let client = IsahcWebPushClient::new()?; Ok(Self { client }) } fn prepare_message( payload: &[u8], signer: &PartialVapidSignatureBuilder, subscription: &SubscriptionInfo, ) -> Result { let signature = signer .clone() .add_sub_info(subscription) .build() .fail("Failed to build VAPID signature")?; let mut message = WebPushMessageBuilder::new(subscription); message.set_payload(ContentEncoding::Aes128Gcm, payload); message.set_vapid_signature(signature); let message = message.build().fail("Failed to build push message")?; Ok(message) } } impl Publish for Publisher { async fn publish( &self, message: M, signer: PartialVapidSignatureBuilder, subscriptions: impl IntoIterator + Send, ) -> Result, Failed> where M: Serialize + Send + 'static, { let payload = serde_json::to_vec_pretty(&message) .fail("Failed to encode web push message to JSON")?; let messages: Vec<_> = subscriptions .into_iter() .map(|sub| Self::prepare_message(&payload, &signer, &sub).map(|message| (sub, message))) .try_collect()?; let deliveries = messages .into_iter() .map(async |(sub, message)| (sub, self.client.send(message).await)); let failures = join_all(deliveries) .await .into_iter() .filter_map(|(sub, result)| result.err().map(|err| (sub, err))) .collect(); Ok(failures) } }