Skip to main content

relay_actions/actions/
read.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    actions::Action, cache::CacheManager, sign::sign_request, sinks::progress::ProgressSink,
5    storage::Storage,
6};
7use relay_lib::prelude::{Address, InboxId, MetaEnvelope, PrivateEnvelope, e2e};
8
9pub struct Read {
10    pub address: Address,
11    pub gid: String,
12    pub silent: bool,
13    pub raw: bool,
14}
15
16impl Action for Read {
17    type Output = (MetaEnvelope, PrivateEnvelope);
18
19    fn execute(
20        &self,
21        storage: &mut Storage,
22        cache: &mut CacheManager,
23        progress: &mut dyn ProgressSink,
24    ) -> Self::Output {
25        let identity = storage
26            .root
27            .get_identity(&self.address)
28            .unwrap_or_else(|| {
29                progress.abort(&format!("No identity found for address `{}`", self.address));
30            })
31            .clone();
32
33        progress.step("Fetching records", "Fetched records");
34        let agent_record = cache
35            .records
36            .agent(&mut cache.agents, self.address.agent())
37            .unwrap_or_else(|e| {
38                progress.error(&format!("{:#?}", e));
39                progress.abort("Failed to fetch agent record");
40            });
41
42        progress.step("Fetching message", "Fetched message");
43        let request = sign_request(
44            &self.address.canonical(),
45            InboxSingleReq {
46                inbox: self.address.inbox().cloned(),
47                gid: self.gid.clone(),
48            },
49            &agent_record,
50            &identity.signing_key(),
51        )
52        .unwrap_or_else(|e| {
53            progress.error(&format!("{:#?}", e));
54            progress.abort("Failed to sign messages request");
55        });
56
57        let resp = reqwest::blocking::Client::new()
58            .get(
59                cache
60                    .agents
61                    .url(self.address.agent())
62                    .unwrap_or_else(|e| {
63                        progress.error(&format!("{:#?}", e));
64                        progress.abort("failed to fetch agent information");
65                    })
66                    .join("/inbox/single")
67                    .unwrap_or_else(|e| {
68                        progress.error(&format!("{:#?}", e));
69                        progress.abort("Failed to construct inbox single URL");
70                    }),
71            )
72            .json(&request)
73            .send()
74            .unwrap_or_else(|e| {
75                progress.error(&format!("{:#?}", e));
76                progress.abort("Failed to send inbox single message request");
77            });
78
79        if resp.status().as_u16() == 404 {
80            progress.abort("Message not found");
81        }
82        if !resp.status().is_success() {
83            progress.abort(&format!(
84                "Failed to fetch message. HTTP {}",
85                resp.status().as_u16()
86            ));
87        }
88        let response = resp.json::<InboxSingleResp>().unwrap_or_else(|e| {
89            progress.error(&format!("{:#?}", e));
90            progress.abort("Failed to parse inbox single message response");
91        });
92        let meta = response.message.clone();
93
94        progress.step("Decrypting message", "Decrypted message");
95        let payload = e2e::decrypt(
96            &identity.static_secret(),
97            &meta.crypto,
98            &meta.payload.payload,
99            &[],
100        )
101        .unwrap_or_else(|e| {
102            progress.error(&format!("{:#?}", e));
103            progress.abort("Failed to decrypt message")
104        });
105
106        progress.step("Parsing message", "Parsed message");
107        let private = serde_json::from_slice::<PrivateEnvelope>(&payload).unwrap_or_else(|e| {
108            progress.error(&format!("{:#?}", e));
109            progress.abort("Failed to parse message payload")
110        });
111
112        progress.step("Displaying message", "Displayed message");
113        if private.content_type != "plain/text" && !self.raw {
114            progress.abort(&format!(
115                "Unsupported content type: {}. Pass `--raw` to see dump content.",
116                private.content_type
117            ));
118        }
119
120        progress.done();
121        (meta, private)
122    }
123}
124
125#[derive(Serialize)]
126struct InboxSingleReq {
127    pub inbox: Option<InboxId>,
128    pub gid: String,
129}
130
131#[derive(Deserialize)]
132struct InboxSingleResp {
133    pub message: MetaEnvelope,
134}