1pub mod v1 {
6 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::{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 use crate::HomeserverToken;
37
38 metadata! {
39 method: PUT,
40 rate_limited: false,
41 authentication: HomeserverToken,
42 path: "/_matrix/app/v1/transactions/{txn_id}",
43 }
44
45 #[request]
47 pub struct Request {
48 #[ruma_api(path)]
52 pub txn_id: OwnedTransactionId,
53
54 pub events: Vec<Raw<AnyTimelineEvent>>,
56
57 #[cfg(feature = "unstable-msc3202")]
59 #[serde(
60 default,
61 skip_serializing_if = "DeviceLists::is_empty",
62 rename = "org.matrix.msc3202.device_lists"
63 )]
64 pub device_lists: DeviceLists,
65
66 #[cfg(feature = "unstable-msc3202")]
69 #[serde(
70 default,
71 skip_serializing_if = "BTreeMap::is_empty",
72 rename = "org.matrix.msc3202.device_one_time_keys_count"
73 )]
74 pub device_one_time_keys_count:
75 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<OneTimeKeyAlgorithm, UInt>>>,
76
77 #[cfg(feature = "unstable-msc3202")]
80 #[serde(
81 default,
82 skip_serializing_if = "BTreeMap::is_empty",
83 rename = "org.matrix.msc3202.device_unused_fallback_key_types"
84 )]
85 pub device_unused_fallback_key_types:
86 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<OneTimeKeyAlgorithm>>>,
87
88 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
90 pub ephemeral: Vec<Raw<EphemeralData>>,
91
92 #[cfg(feature = "unstable-msc4203")]
94 #[serde(
95 default,
96 skip_serializing_if = "<[_]>::is_empty",
97 rename = "de.sorunome.msc2409.to_device"
98 )]
99 pub to_device: Vec<Raw<AnyAppserviceToDeviceEvent>>,
100 }
101
102 #[response]
104 #[derive(Default)]
105 pub struct Response {}
106
107 impl Request {
108 pub fn new(txn_id: OwnedTransactionId, events: Vec<Raw<AnyTimelineEvent>>) -> Request {
110 Request {
111 txn_id,
112 events,
113 #[cfg(feature = "unstable-msc3202")]
114 device_lists: DeviceLists::new(),
115 #[cfg(feature = "unstable-msc3202")]
116 device_one_time_keys_count: BTreeMap::new(),
117 #[cfg(feature = "unstable-msc3202")]
118 device_unused_fallback_key_types: BTreeMap::new(),
119 ephemeral: Vec::new(),
120 #[cfg(feature = "unstable-msc4203")]
121 to_device: Vec::new(),
122 }
123 }
124 }
125
126 impl Response {
127 pub fn new() -> Self {
129 Self {}
130 }
131 }
132
133 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
135 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
136 #[cfg(feature = "unstable-msc3202")]
137 pub struct DeviceLists {
138 #[serde(default, skip_serializing_if = "Vec::is_empty")]
141 pub changed: Vec<OwnedUserId>,
142
143 #[serde(default, skip_serializing_if = "Vec::is_empty")]
146 pub left: Vec<OwnedUserId>,
147 }
148
149 #[cfg(feature = "unstable-msc3202")]
150 impl DeviceLists {
151 pub fn new() -> Self {
153 Default::default()
154 }
155
156 pub fn is_empty(&self) -> bool {
158 let Self { changed, left } = self;
159 changed.is_empty() && left.is_empty()
160 }
161 }
162
163 #[derive(Clone, Debug, Serialize)]
165 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
166 #[serde(untagged)]
167 pub enum EphemeralData {
168 Presence(PresenceEvent),
170
171 Receipt(ReceiptEvent),
173
174 Typing(TypingEvent),
176
177 #[doc(hidden)]
178 _Custom(_CustomEphemeralData),
179 }
180
181 impl EphemeralData {
182 pub fn data_type(&self) -> &str {
184 match self {
185 Self::Presence(_) => "m.presence",
186 Self::Receipt(_) => "m.receipt",
187 Self::Typing(_) => "m.typing",
188 Self::_Custom(c) => &c.data_type,
189 }
190 }
191
192 pub fn data(&self) -> Cow<'_, JsonObject> {
197 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
198 match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
199 JsonValue::Object(obj) => obj,
200 _ => panic!("all ephemeral data types must serialize to objects"),
201 }
202 }
203
204 match self {
205 Self::Presence(d) => Cow::Owned(serialize(d)),
206 Self::Receipt(d) => Cow::Owned(serialize(d)),
207 Self::Typing(d) => Cow::Owned(serialize(d)),
208 Self::_Custom(c) => Cow::Borrowed(&c.data),
209 }
210 }
211 }
212
213 impl<'de> Deserialize<'de> for EphemeralData {
214 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215 where
216 D: Deserializer<'de>,
217 {
218 #[derive(Deserialize)]
219 struct EphemeralDataDeHelper {
220 #[serde(rename = "type")]
222 data_type: String,
223 }
224
225 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
226 let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
227
228 Ok(match data_type.as_ref() {
229 "m.presence" => Self::Presence(from_raw_json_value(&json)?),
230 "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
231 "m.typing" => Self::Typing(from_raw_json_value(&json)?),
232 _ => Self::_Custom(_CustomEphemeralData {
233 data_type,
234 data: from_raw_json_value(&json)?,
235 }),
236 })
237 }
238 }
239
240 #[doc(hidden)]
242 #[derive(Debug, Clone)]
243 pub struct _CustomEphemeralData {
244 data_type: String,
246 data: JsonObject,
248 }
249
250 impl Serialize for _CustomEphemeralData {
251 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
252 where
253 S: serde::Serializer,
254 {
255 self.data.serialize(serializer)
256 }
257 }
258
259 #[derive(Clone, Debug)]
262 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
263 #[cfg(feature = "unstable-msc4203")]
264 pub struct AnyAppserviceToDeviceEvent {
265 pub event: AnyToDeviceEvent,
267
268 pub to_user_id: OwnedUserId,
270
271 pub to_device_id: OwnedDeviceId,
273 }
274
275 #[cfg(feature = "unstable-msc4203")]
276 impl AnyAppserviceToDeviceEvent {
277 pub fn new(
280 event: AnyToDeviceEvent,
281 to_user_id: OwnedUserId,
282 to_device_id: OwnedDeviceId,
283 ) -> Self {
284 Self { event, to_user_id, to_device_id }
285 }
286
287 pub fn sender(&self) -> &UserId {
289 self.event.sender()
290 }
291
292 pub fn event_type(&self) -> ToDeviceEventType {
294 self.event.event_type()
295 }
296
297 pub fn content(&self) -> AnyToDeviceEventContent {
299 self.event.content()
300 }
301 }
302
303 #[cfg(feature = "unstable-msc4203")]
304 impl<'de> Deserialize<'de> for AnyAppserviceToDeviceEvent {
305 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
306 where
307 D: Deserializer<'de>,
308 {
309 #[derive(Deserialize)]
310 struct AppserviceFields {
311 to_user_id: OwnedUserId,
312 to_device_id: OwnedDeviceId,
313 }
314
315 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
316
317 let event = from_raw_json_value(&json)?;
318
319 let AppserviceFields { to_user_id, to_device_id } = from_raw_json_value(&json)?;
320
321 Ok(AnyAppserviceToDeviceEvent::new(event, to_user_id, to_device_id))
322 }
323 }
324
325 #[cfg(feature = "unstable-msc4203")]
326 impl JsonCastable<JsonObject> for AnyAppserviceToDeviceEvent {}
327 #[cfg(feature = "unstable-msc4203")]
328 impl JsonCastable<AnyToDeviceEvent> for AnyAppserviceToDeviceEvent {}
329
330 #[cfg(test)]
331 mod tests {
332 use assert_matches2::assert_let;
333 use js_int::uint;
334 use ruma_common::{
335 MilliSecondsSinceUnixEpoch, canonical_json::assert_to_canonical_json_eq, event_id,
336 room_id, user_id,
337 };
338 use ruma_events::receipt::ReceiptType;
339 use serde_json::{from_value as from_json_value, json};
340
341 use super::EphemeralData;
342
343 #[cfg(feature = "client")]
344 #[test]
345 fn request_contains_events_field() {
346 use ruma_common::api::OutgoingRequestExt as _;
347
348 let dummy_event_json = json!({
349 "type": "m.room.message",
350 "event_id": "$143273582443PhrSn:example.com",
351 "origin_server_ts": 1,
352 "room_id": "!roomid:room.com",
353 "sender": "@user:example.com",
354 "content": {
355 "body": "test",
356 "msgtype": "m.text",
357 },
358 });
359 let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
360 let events = vec![dummy_event];
361
362 let req = super::Request::new("any_txn_id".into(), events)
363 .try_into_http_request::<Vec<u8>>("https://homeserver.tld", "auth_tok", ())
364 .unwrap();
365 let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
366
367 assert_eq!(
368 json_body,
369 json!({
370 "events": [
371 dummy_event_json,
372 ]
373 })
374 );
375 }
376
377 #[test]
378 fn serde_ephemeral_data() {
379 let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
380 let user_id = user_id!("@alice:server.local");
381 let event_id = event_id!("$1435641916114394fHBL");
382
383 let typing_json = json!({
385 "type": "m.typing",
386 "room_id": room_id,
387 "content": {
388 "user_ids": [user_id],
389 },
390 });
391
392 let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
393 assert_let!(EphemeralData::Typing(typing) = &data);
394 assert_eq!(typing.room_id, room_id);
395 assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
396
397 assert_to_canonical_json_eq!(data, typing_json);
398
399 let receipt_json = json!({
401 "type": "m.receipt",
402 "room_id": room_id,
403 "content": {
404 event_id: {
405 "m.read": {
406 user_id: {
407 "ts": 453,
408 },
409 },
410 },
411 },
412 });
413
414 let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
415 assert_let!(EphemeralData::Receipt(receipt) = &data);
416 assert_eq!(receipt.room_id, room_id);
417 let event_receipts = receipt.content.get(event_id).unwrap();
418 let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
419 let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
420 assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
421
422 assert_to_canonical_json_eq!(data, receipt_json);
423
424 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_let!(EphemeralData::Presence(presence) = &data);
439 assert_eq!(presence.sender, user_id);
440 assert_eq!(presence.content.currently_active, Some(false));
441
442 assert_to_canonical_json_eq!(data, presence_json);
443
444 let custom_json = json!({
446 "type": "dev.ruma.custom",
447 "key": "value",
448 "content": {
449 "foo": "bar",
450 },
451 });
452
453 let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
454
455 assert_to_canonical_json_eq!(data, custom_json);
456 }
457
458 #[test]
459 #[cfg(feature = "unstable-msc4203")]
460 fn serde_any_appservice_to_device_event() {
461 use ruma_common::{device_id, user_id};
462
463 use super::AnyAppserviceToDeviceEvent;
464
465 let event_json = json!({
466 "type": "m.key.verification.request",
467 "sender": "@alice:example.org",
468 "content": {
469 "from_device": "AliceDevice2",
470 "methods": [
471 "m.sas.v1"
472 ],
473 "timestamp": 1_559_598_944_869_i64,
474 "transaction_id": "S0meUniqueAndOpaqueString"
475 },
476 "to_user_id": "@bob:example.org",
477 "to_device_id": "DEVICEID"
478 });
479
480 let event = from_json_value::<AnyAppserviceToDeviceEvent>(event_json.clone()).unwrap();
482 assert_eq!(event.sender(), user_id!("@alice:example.org"));
483 assert_eq!(event.to_user_id, user_id!("@bob:example.org"));
484 assert_eq!(event.to_device_id, device_id!("DEVICEID"));
485 assert_eq!(event.event_type().to_string(), "m.key.verification.request");
486 }
487 }
488}