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