Skip to main content

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/v1.19/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            let Self { changed, left } = self;
157            changed.is_empty() && left.is_empty()
158        }
159    }
160
161    /// Type for passing ephemeral data to application services.
162    #[derive(Clone, Debug, Serialize)]
163    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
164    #[serde(untagged)]
165    pub enum EphemeralData {
166        /// A presence update for a user.
167        Presence(PresenceEvent),
168
169        /// A receipt update for a room.
170        Receipt(ReceiptEvent),
171
172        /// A typing notification update for a room.
173        Typing(TypingEvent),
174
175        #[doc(hidden)]
176        _Custom(_CustomEphemeralData),
177    }
178
179    impl EphemeralData {
180        /// A reference to the `type` string of the data.
181        pub fn data_type(&self) -> &str {
182            match self {
183                Self::Presence(_) => "m.presence",
184                Self::Receipt(_) => "m.receipt",
185                Self::Typing(_) => "m.typing",
186                Self::_Custom(c) => &c.data_type,
187            }
188        }
189
190        /// The data as a JSON object.
191        ///
192        /// Prefer to use the public variants of `EphemeralData` where possible; this method is
193        /// meant to be used for unsupported data types only.
194        pub fn data(&self) -> Cow<'_, JsonObject> {
195            fn serialize<T: Serialize>(obj: &T) -> JsonObject {
196                match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
197                    JsonValue::Object(obj) => obj,
198                    _ => panic!("all ephemeral data types must serialize to objects"),
199                }
200            }
201
202            match self {
203                Self::Presence(d) => Cow::Owned(serialize(d)),
204                Self::Receipt(d) => Cow::Owned(serialize(d)),
205                Self::Typing(d) => Cow::Owned(serialize(d)),
206                Self::_Custom(c) => Cow::Borrowed(&c.data),
207            }
208        }
209    }
210
211    impl<'de> Deserialize<'de> for EphemeralData {
212        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213        where
214            D: Deserializer<'de>,
215        {
216            #[derive(Deserialize)]
217            struct EphemeralDataDeHelper {
218                /// The data type.
219                #[serde(rename = "type")]
220                data_type: String,
221            }
222
223            let json = Box::<RawJsonValue>::deserialize(deserializer)?;
224            let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
225
226            Ok(match data_type.as_ref() {
227                "m.presence" => Self::Presence(from_raw_json_value(&json)?),
228                "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
229                "m.typing" => Self::Typing(from_raw_json_value(&json)?),
230                _ => Self::_Custom(_CustomEphemeralData {
231                    data_type,
232                    data: from_raw_json_value(&json)?,
233                }),
234            })
235        }
236    }
237
238    /// Ephemeral data with an unknown type.
239    #[doc(hidden)]
240    #[derive(Debug, Clone)]
241    pub struct _CustomEphemeralData {
242        /// The type of the data.
243        data_type: String,
244        /// The data.
245        data: JsonObject,
246    }
247
248    impl Serialize for _CustomEphemeralData {
249        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250        where
251            S: serde::Serializer,
252        {
253            self.data.serialize(serializer)
254        }
255    }
256
257    /// An event sent using send-to-device messaging with additional fields when pushed to an
258    /// application service.
259    #[derive(Clone, Debug)]
260    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261    #[cfg(feature = "unstable-msc4203")]
262    pub struct AnyAppserviceToDeviceEvent {
263        /// The to-device event.
264        pub event: AnyToDeviceEvent,
265
266        /// The fully-qualified user ID of the intended recipient.
267        pub to_user_id: OwnedUserId,
268
269        /// The device ID of the intended recipient.
270        pub to_device_id: OwnedDeviceId,
271    }
272
273    #[cfg(feature = "unstable-msc4203")]
274    impl AnyAppserviceToDeviceEvent {
275        /// Construct a new `AnyAppserviceToDeviceEvent` with the given event and recipient
276        /// information.
277        pub fn new(
278            event: AnyToDeviceEvent,
279            to_user_id: OwnedUserId,
280            to_device_id: OwnedDeviceId,
281        ) -> Self {
282            Self { event, to_user_id, to_device_id }
283        }
284
285        /// The fully-qualified ID of the user who sent this event.
286        pub fn sender(&self) -> &UserId {
287            self.event.sender()
288        }
289
290        /// The event type of the to-device event.
291        pub fn event_type(&self) -> ToDeviceEventType {
292            self.event.event_type()
293        }
294
295        /// The content of the to-device event.
296        pub fn content(&self) -> AnyToDeviceEventContent {
297            self.event.content()
298        }
299    }
300
301    #[cfg(feature = "unstable-msc4203")]
302    impl<'de> Deserialize<'de> for AnyAppserviceToDeviceEvent {
303        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304        where
305            D: Deserializer<'de>,
306        {
307            #[derive(Deserialize)]
308            struct AppserviceFields {
309                to_user_id: OwnedUserId,
310                to_device_id: OwnedDeviceId,
311            }
312
313            let json = Box::<RawJsonValue>::deserialize(deserializer)?;
314
315            let event = from_raw_json_value(&json)?;
316
317            let AppserviceFields { to_user_id, to_device_id } = from_raw_json_value(&json)?;
318
319            Ok(AnyAppserviceToDeviceEvent::new(event, to_user_id, to_device_id))
320        }
321    }
322
323    #[cfg(feature = "unstable-msc4203")]
324    impl JsonCastable<JsonObject> for AnyAppserviceToDeviceEvent {}
325    #[cfg(feature = "unstable-msc4203")]
326    impl JsonCastable<AnyToDeviceEvent> for AnyAppserviceToDeviceEvent {}
327
328    #[cfg(test)]
329    mod tests {
330        use assert_matches2::assert_let;
331        use js_int::uint;
332        use ruma_common::{
333            MilliSecondsSinceUnixEpoch, canonical_json::assert_to_canonical_json_eq, event_id,
334            room_id, user_id,
335        };
336        use ruma_events::receipt::ReceiptType;
337        use serde_json::{from_value as from_json_value, json};
338
339        use super::EphemeralData;
340
341        #[cfg(feature = "client")]
342        #[test]
343        fn request_contains_events_field() {
344            use ruma_common::api::{OutgoingRequest, auth_scheme::SendAccessToken};
345
346            let dummy_event_json = json!({
347                "type": "m.room.message",
348                "event_id": "$143273582443PhrSn:example.com",
349                "origin_server_ts": 1,
350                "room_id": "!roomid:room.com",
351                "sender": "@user:example.com",
352                "content": {
353                    "body": "test",
354                    "msgtype": "m.text",
355                },
356            });
357            let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
358            let events = vec![dummy_event];
359
360            let req = super::Request::new("any_txn_id".into(), events)
361                .try_into_http_request::<Vec<u8>>(
362                    "https://homeserver.tld",
363                    SendAccessToken::IfRequired("auth_tok"),
364                    (),
365                )
366                .unwrap();
367            let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
368
369            assert_eq!(
370                json_body,
371                json!({
372                    "events": [
373                        dummy_event_json,
374                    ]
375                })
376            );
377        }
378
379        #[test]
380        fn serde_ephemeral_data() {
381            let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
382            let user_id = user_id!("@alice:server.local");
383            let event_id = event_id!("$1435641916114394fHBL");
384
385            // Test m.typing serde.
386            let typing_json = json!({
387                "type": "m.typing",
388                "room_id": room_id,
389                "content": {
390                    "user_ids": [user_id],
391                },
392            });
393
394            let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
395            assert_let!(EphemeralData::Typing(typing) = &data);
396            assert_eq!(typing.room_id, room_id);
397            assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
398
399            assert_to_canonical_json_eq!(data, typing_json);
400
401            // Test m.receipt serde.
402            let receipt_json = json!({
403                "type": "m.receipt",
404                "room_id": room_id,
405                "content": {
406                    event_id: {
407                        "m.read": {
408                            user_id: {
409                                "ts": 453,
410                            },
411                        },
412                    },
413                },
414            });
415
416            let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
417            assert_let!(EphemeralData::Receipt(receipt) = &data);
418            assert_eq!(receipt.room_id, room_id);
419            let event_receipts = receipt.content.get(event_id).unwrap();
420            let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
421            let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
422            assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
423
424            assert_to_canonical_json_eq!(data, receipt_json);
425
426            // Test m.presence serde.
427            let presence_json = json!({
428                "type": "m.presence",
429                "sender": user_id,
430                "content": {
431                    "avatar_url": "mxc://localhost/wefuiwegh8742w",
432                    "currently_active": false,
433                    "last_active_ago": 785,
434                    "presence": "online",
435                    "status_msg": "Making cupcakes",
436                },
437            });
438
439            let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
440            assert_let!(EphemeralData::Presence(presence) = &data);
441            assert_eq!(presence.sender, user_id);
442            assert_eq!(presence.content.currently_active, Some(false));
443
444            assert_to_canonical_json_eq!(data, presence_json);
445
446            // Test custom serde.
447            let custom_json = json!({
448                "type": "dev.ruma.custom",
449                "key": "value",
450                "content": {
451                    "foo": "bar",
452                },
453            });
454
455            let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
456
457            assert_to_canonical_json_eq!(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}