summaryrefslogtreecommitdiff
path: root/src/push/publisher.rs
blob: 4092724a1c873be2b5dd10633a585c6bb9ddc876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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<M>(
        &self,
        message: M,
        signer: PartialVapidSignatureBuilder,
        subscriptions: impl IntoIterator<Item = SubscriptionInfo> + Send,
    ) -> impl Future<Output = Result<Vec<(SubscriptionInfo, WebPushError)>, Failed>> + Send
    where
        M: Serialize + Send + 'static;
}

#[derive(Clone)]
pub struct Publisher {
    client: IsahcWebPushClient,
}

impl Publisher {
    pub fn new() -> Result<Self, WebPushError> {
        let client = IsahcWebPushClient::new()?;
        Ok(Self { client })
    }

    fn prepare_message(
        payload: &[u8],
        signer: &PartialVapidSignatureBuilder,
        subscription: &SubscriptionInfo,
    ) -> Result<WebPushMessage, Failed> {
        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<M>(
        &self,
        message: M,
        signer: PartialVapidSignatureBuilder,
        subscriptions: impl IntoIterator<Item = SubscriptionInfo> + Send,
    ) -> Result<Vec<(SubscriptionInfo, WebPushError)>, 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)
    }
}