ruma_appservice_api/event/
push_events.rs

1//! `PUT /_matrix/app/*/transactions/{txnId}`
2//!
3//! Endpoint to push an event (or batch of events) to the application service.
4
5pub mod v1 {
6    //! `/v1/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/latest/application-service-api/#put_matrixappv1transactionstxnid
9
10    use std::borrow::Cow;
11    #[cfg(feature = "unstable-msc3202")]
12    use std::collections::BTreeMap;
13
14    #[cfg(feature = "unstable-msc3202")]
15    use js_int::UInt;
16    #[cfg(feature = "unstable-msc3202")]
17    use ruma_common::OwnedUserId;
18    use ruma_common::{
19        api::{auth_scheme::AccessToken, request, response},
20        metadata,
21        serde::{from_raw_json_value, JsonObject, Raw},
22        OwnedTransactionId,
23    };
24    #[cfg(feature = "unstable-msc3202")]
25    use ruma_common::{OneTimeKeyAlgorithm, OwnedDeviceId};
26    #[cfg(feature = "unstable-msc4203")]
27    use ruma_events::AnyToDeviceEvent;
28    use ruma_events::{
29        presence::PresenceEvent, receipt::ReceiptEvent, typing::TypingEvent, AnyTimelineEvent,
30    };
31    use serde::{Deserialize, Deserializer, Serialize};
32    use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
33
34    metadata! {
35        method: PUT,
36        rate_limited: false,
37        authentication: AccessToken,
38        path: "/_matrix/app/v1/transactions/{txn_id}",
39    }
40
41    /// Request type for the `push_events` endpoint.
42    #[request]
43    pub struct Request {
44        /// The transaction ID for this set of events.
45        ///
46        /// Homeservers generate these IDs and they are used to ensure idempotency of results.
47        #[ruma_api(path)]
48        pub txn_id: OwnedTransactionId,
49
50        /// A list of events.
51        pub events: Vec<Raw<AnyTimelineEvent>>,
52
53        /// Information on E2E device updates.
54        #[cfg(feature = "unstable-msc3202")]
55        #[serde(
56            default,
57            skip_serializing_if = "DeviceLists::is_empty",
58            rename = "org.matrix.msc3202.device_lists"
59        )]
60        pub device_lists: DeviceLists,
61
62        /// The number of unclaimed one-time keys currently held on the server for this device, for
63        /// each algorithm.
64        #[cfg(feature = "unstable-msc3202")]
65        #[serde(
66            default,
67            skip_serializing_if = "BTreeMap::is_empty",
68            rename = "org.matrix.msc3202.device_one_time_keys_count"
69        )]
70        pub device_one_time_keys_count:
71            BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<OneTimeKeyAlgorithm, UInt>>>,
72
73        /// A list of key algorithms for which the server has an unused fallback key for the
74        /// device.
75        #[cfg(feature = "unstable-msc3202")]
76        #[serde(
77            default,
78            skip_serializing_if = "BTreeMap::is_empty",
79            rename = "org.matrix.msc3202.device_unused_fallback_key_types"
80        )]
81        pub device_unused_fallback_key_types:
82            BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<OneTimeKeyAlgorithm>>>,
83
84        /// A list of ephemeral data.
85        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
86        pub ephemeral: Vec<Raw<EphemeralData>>,
87
88        /// A list of to-device messages.
89        #[cfg(feature = "unstable-msc4203")]
90        #[serde(
91            default,
92            skip_serializing_if = "<[_]>::is_empty",
93            rename = "de.sorunome.msc2409.to_device"
94        )]
95        pub to_device: Vec<Raw<AnyToDeviceEvent>>,
96    }
97
98    /// Response type for the `push_events` endpoint.
99    #[response]
100    #[derive(Default)]
101    pub struct Response {}
102
103    impl Request {
104        /// Creates an `Request` with the given transaction ID and list of events.
105        pub fn new(txn_id: OwnedTransactionId, events: Vec<Raw<AnyTimelineEvent>>) -> Request {
106            Request {
107                txn_id,
108                events,
109                #[cfg(feature = "unstable-msc3202")]
110                device_lists: DeviceLists::new(),
111                #[cfg(feature = "unstable-msc3202")]
112                device_one_time_keys_count: BTreeMap::new(),
113                #[cfg(feature = "unstable-msc3202")]
114                device_unused_fallback_key_types: BTreeMap::new(),
115                ephemeral: Vec::new(),
116                #[cfg(feature = "unstable-msc4203")]
117                to_device: Vec::new(),
118            }
119        }
120    }
121
122    impl Response {
123        /// Creates an empty `Response`.
124        pub fn new() -> Self {
125            Self {}
126        }
127    }
128
129    /// Information on E2E device updates.
130    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
131    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
132    #[cfg(feature = "unstable-msc3202")]
133    pub struct DeviceLists {
134        /// List of users who have updated their device identity keys or who now
135        /// share an encrypted room with the client since the previous sync.
136        #[serde(default, skip_serializing_if = "Vec::is_empty")]
137        pub changed: Vec<OwnedUserId>,
138
139        /// List of users who no longer share encrypted rooms since the previous sync
140        /// response.
141        #[serde(default, skip_serializing_if = "Vec::is_empty")]
142        pub left: Vec<OwnedUserId>,
143    }
144
145    #[cfg(feature = "unstable-msc3202")]
146    impl DeviceLists {
147        /// Creates an empty `DeviceLists`.
148        pub fn new() -> Self {
149            Default::default()
150        }
151
152        /// Returns true if there are no device list updates.
153        pub fn is_empty(&self) -> bool {
154            self.changed.is_empty() && self.left.is_empty()
155        }
156    }
157
158    /// Type for passing ephemeral data to application services.
159    #[derive(Clone, Debug, Serialize)]
160    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
161    #[serde(untagged)]
162    pub enum EphemeralData {
163        /// A presence update for a user.
164        Presence(PresenceEvent),
165
166        /// A receipt update for a room.
167        Receipt(ReceiptEvent),
168
169        /// A typing notification update for a room.
170        Typing(TypingEvent),
171
172        #[doc(hidden)]
173        _Custom(_CustomEphemeralData),
174    }
175
176    impl EphemeralData {
177        /// A reference to the `type` string of the data.
178        pub fn data_type(&self) -> &str {
179            match self {
180                Self::Presence(_) => "m.presence",
181                Self::Receipt(_) => "m.receipt",
182                Self::Typing(_) => "m.typing",
183                Self::_Custom(c) => &c.data_type,
184            }
185        }
186
187        /// The data as a JSON object.
188        ///
189        /// Prefer to use the public variants of `EphemeralData` where possible; this method is
190        /// meant to be used for unsupported data types only.
191        pub fn data(&self) -> Cow<'_, JsonObject> {
192            fn serialize<T: Serialize>(obj: &T) -> JsonObject {
193                match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
194                    JsonValue::Object(obj) => obj,
195                    _ => panic!("all ephemeral data types must serialize to objects"),
196                }
197            }
198
199            match self {
200                Self::Presence(d) => Cow::Owned(serialize(d)),
201                Self::Receipt(d) => Cow::Owned(serialize(d)),
202                Self::Typing(d) => Cow::Owned(serialize(d)),
203                Self::_Custom(c) => Cow::Borrowed(&c.data),
204            }
205        }
206    }
207
208    impl<'de> Deserialize<'de> for EphemeralData {
209        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210        where
211            D: Deserializer<'de>,
212        {
213            #[derive(Deserialize)]
214            struct EphemeralDataDeHelper {
215                /// The data type.
216                #[serde(rename = "type")]
217                data_type: String,
218            }
219
220            let json = Box::<RawJsonValue>::deserialize(deserializer)?;
221            let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
222
223            Ok(match data_type.as_ref() {
224                "m.presence" => Self::Presence(from_raw_json_value(&json)?),
225                "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
226                "m.typing" => Self::Typing(from_raw_json_value(&json)?),
227                _ => Self::_Custom(_CustomEphemeralData {
228                    data_type,
229                    data: from_raw_json_value(&json)?,
230                }),
231            })
232        }
233    }
234
235    /// Ephemeral data with an unknown type.
236    #[doc(hidden)]
237    #[derive(Debug, Clone)]
238    pub struct _CustomEphemeralData {
239        /// The type of the data.
240        data_type: String,
241        /// The data.
242        data: JsonObject,
243    }
244
245    impl Serialize for _CustomEphemeralData {
246        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247        where
248            S: serde::Serializer,
249        {
250            self.data.serialize(serializer)
251        }
252    }
253
254    #[cfg(test)]
255    mod tests {
256        use assert_matches2::assert_matches;
257        use js_int::uint;
258        use ruma_common::{event_id, room_id, user_id, MilliSecondsSinceUnixEpoch};
259        use ruma_events::receipt::ReceiptType;
260        use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
261
262        use super::EphemeralData;
263
264        #[cfg(feature = "client")]
265        #[test]
266        fn request_contains_events_field() {
267            use ruma_common::api::{auth_scheme::SendAccessToken, OutgoingRequest};
268
269            let dummy_event_json = json!({
270                "type": "m.room.message",
271                "event_id": "$143273582443PhrSn:example.com",
272                "origin_server_ts": 1,
273                "room_id": "!roomid:room.com",
274                "sender": "@user:example.com",
275                "content": {
276                    "body": "test",
277                    "msgtype": "m.text",
278                },
279            });
280            let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
281            let events = vec![dummy_event];
282
283            let req = super::Request::new("any_txn_id".into(), events)
284                .try_into_http_request::<Vec<u8>>(
285                    "https://homeserver.tld",
286                    SendAccessToken::IfRequired("auth_tok"),
287                    (),
288                )
289                .unwrap();
290            let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
291
292            assert_eq!(
293                json_body,
294                json!({
295                    "events": [
296                        dummy_event_json,
297                    ]
298                })
299            );
300        }
301
302        #[test]
303        fn serde_ephemeral_data() {
304            let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
305            let user_id = user_id!("@alice:server.local");
306            let event_id = event_id!("$1435641916114394fHBL");
307
308            // Test m.typing serde.
309            let typing_json = json!({
310                "type": "m.typing",
311                "room_id": room_id,
312                "content": {
313                    "user_ids": [user_id],
314                },
315            });
316
317            let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
318            assert_matches!(&data, EphemeralData::Typing(typing));
319            assert_eq!(typing.room_id, room_id);
320            assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
321
322            let serialized_data = to_json_value(data).unwrap();
323            assert_eq!(serialized_data, typing_json);
324
325            // Test m.receipt serde.
326            let receipt_json = json!({
327                "type": "m.receipt",
328                "room_id": room_id,
329                "content": {
330                    event_id: {
331                        "m.read": {
332                            user_id: {
333                                "ts": 453,
334                            },
335                        },
336                    },
337                },
338            });
339
340            let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
341            assert_matches!(&data, EphemeralData::Receipt(receipt));
342            assert_eq!(receipt.room_id, room_id);
343            let event_receipts = receipt.content.get(event_id).unwrap();
344            let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
345            let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
346            assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
347
348            let serialized_data = to_json_value(data).unwrap();
349            assert_eq!(serialized_data, receipt_json);
350
351            // Test m.presence serde.
352            let presence_json = json!({
353                "type": "m.presence",
354                "sender": user_id,
355                "content": {
356                    "avatar_url": "mxc://localhost/wefuiwegh8742w",
357                    "currently_active": false,
358                    "last_active_ago": 785,
359                    "presence": "online",
360                    "status_msg": "Making cupcakes",
361                },
362            });
363
364            let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
365            assert_matches!(&data, EphemeralData::Presence(presence));
366            assert_eq!(presence.sender, user_id);
367            assert_eq!(presence.content.currently_active, Some(false));
368
369            let serialized_data = to_json_value(data).unwrap();
370            assert_eq!(serialized_data, presence_json);
371
372            // Test custom serde.
373            let custom_json = json!({
374                "type": "dev.ruma.custom",
375                "key": "value",
376                "content": {
377                    "foo": "bar",
378                },
379            });
380
381            let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
382
383            let serialized_data = to_json_value(data).unwrap();
384            assert_eq!(serialized_data, custom_json);
385        }
386    }
387}