2025-10-27 07:20:23 +00:00
|
|
|
use axum::extract::State;
|
2026-04-23 20:02:48 +01:00
|
|
|
use axum_client_ip::ClientIp;
|
2025-10-27 07:20:23 +00:00
|
|
|
use conduwuit::{Err, Result, at};
|
|
|
|
|
use futures::StreamExt;
|
2026-04-10 14:01:22 -04:00
|
|
|
use ruma::{
|
|
|
|
|
api::client::dehydrated_device::{
|
|
|
|
|
delete_dehydrated_device::unstable as delete_dehydrated_device,
|
|
|
|
|
get_dehydrated_device::unstable as get_dehydrated_device,
|
|
|
|
|
get_events::unstable as get_events,
|
|
|
|
|
put_dehydrated_device::unstable as put_dehydrated_device,
|
|
|
|
|
},
|
|
|
|
|
assign,
|
2025-10-27 07:20:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use crate::Ruma;
|
|
|
|
|
|
|
|
|
|
const MAX_BATCH_EVENTS: usize = 50;
|
|
|
|
|
|
|
|
|
|
/// # `PUT /_matrix/client/../dehydrated_device`
|
|
|
|
|
///
|
|
|
|
|
/// Creates or overwrites the user's dehydrated device.
|
|
|
|
|
#[tracing::instrument(skip_all, fields(%client))]
|
|
|
|
|
pub(crate) async fn put_dehydrated_device_route(
|
|
|
|
|
State(services): State<crate::State>,
|
2026-04-23 20:02:48 +01:00
|
|
|
ClientIp(client): ClientIp,
|
2025-10-27 07:20:23 +00:00
|
|
|
body: Ruma<put_dehydrated_device::Request>,
|
|
|
|
|
) -> Result<put_dehydrated_device::Response> {
|
|
|
|
|
let sender_user = body
|
|
|
|
|
.sender_user
|
|
|
|
|
.as_deref()
|
|
|
|
|
.expect("AccessToken authentication required");
|
|
|
|
|
|
|
|
|
|
let device_id = body.body.device_id.clone();
|
|
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.users
|
|
|
|
|
.set_dehydrated_device(sender_user, body.body)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2026-03-29 12:25:42 -04:00
|
|
|
Ok(put_dehydrated_device::Response::new(device_id))
|
2025-10-27 07:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// # `DELETE /_matrix/client/../dehydrated_device`
|
|
|
|
|
///
|
|
|
|
|
/// Deletes the user's dehydrated device without replacement.
|
|
|
|
|
#[tracing::instrument(skip_all, fields(%client))]
|
|
|
|
|
pub(crate) async fn delete_dehydrated_device_route(
|
|
|
|
|
State(services): State<crate::State>,
|
2026-04-23 20:02:48 +01:00
|
|
|
ClientIp(client): ClientIp,
|
2025-10-27 07:20:23 +00:00
|
|
|
body: Ruma<delete_dehydrated_device::Request>,
|
|
|
|
|
) -> Result<delete_dehydrated_device::Response> {
|
|
|
|
|
let sender_user = body.sender_user();
|
|
|
|
|
|
|
|
|
|
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
|
|
|
|
|
|
|
|
|
|
services.users.remove_device(sender_user, &device_id).await;
|
|
|
|
|
|
2026-03-29 12:25:42 -04:00
|
|
|
Ok(delete_dehydrated_device::Response::new(device_id))
|
2025-10-27 07:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// # `GET /_matrix/client/../dehydrated_device`
|
|
|
|
|
///
|
|
|
|
|
/// Gets the user's dehydrated device
|
|
|
|
|
#[tracing::instrument(skip_all, fields(%client))]
|
|
|
|
|
pub(crate) async fn get_dehydrated_device_route(
|
|
|
|
|
State(services): State<crate::State>,
|
2026-04-23 20:02:48 +01:00
|
|
|
ClientIp(client): ClientIp,
|
2025-10-27 07:20:23 +00:00
|
|
|
body: Ruma<get_dehydrated_device::Request>,
|
|
|
|
|
) -> Result<get_dehydrated_device::Response> {
|
|
|
|
|
let sender_user = body.sender_user();
|
|
|
|
|
|
|
|
|
|
let device = services.users.get_dehydrated_device(sender_user).await?;
|
|
|
|
|
|
2026-03-29 12:25:42 -04:00
|
|
|
Ok(get_dehydrated_device::Response::new(device.device_id, device.device_data))
|
2025-10-27 07:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
|
|
|
|
|
///
|
|
|
|
|
/// Paginates the events of the dehydrated device.
|
|
|
|
|
#[tracing::instrument(skip_all, fields(%client))]
|
|
|
|
|
pub(crate) async fn get_dehydrated_events_route(
|
|
|
|
|
State(services): State<crate::State>,
|
2026-04-23 20:02:48 +01:00
|
|
|
ClientIp(client): ClientIp,
|
2025-10-27 07:20:23 +00:00
|
|
|
body: Ruma<get_events::Request>,
|
|
|
|
|
) -> Result<get_events::Response> {
|
|
|
|
|
let sender_user = body.sender_user();
|
|
|
|
|
|
|
|
|
|
let device_id = &body.body.device_id;
|
|
|
|
|
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
|
|
|
|
|
|
|
|
|
|
if existing_id.as_ref().is_err()
|
|
|
|
|
|| existing_id
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_ok_and(|existing_id| existing_id != device_id)
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let since: Option<u64> = body
|
|
|
|
|
.body
|
|
|
|
|
.next_batch
|
|
|
|
|
.as_deref()
|
|
|
|
|
.map(str::parse)
|
|
|
|
|
.transpose()?;
|
|
|
|
|
|
|
|
|
|
let mut next_batch: Option<u64> = None;
|
|
|
|
|
let events = services
|
|
|
|
|
.users
|
|
|
|
|
.get_to_device_events(sender_user, device_id, since, None)
|
|
|
|
|
.take(MAX_BATCH_EVENTS)
|
|
|
|
|
.inspect(|&(count, _)| {
|
|
|
|
|
next_batch.replace(count);
|
|
|
|
|
})
|
|
|
|
|
.map(at!(1))
|
|
|
|
|
.collect()
|
|
|
|
|
.await;
|
|
|
|
|
|
2026-04-10 14:01:22 -04:00
|
|
|
Ok(assign!(get_events::Response::new(events), {
|
2025-10-27 07:20:23 +00:00
|
|
|
next_batch: next_batch.as_ref().map(ToString::to_string),
|
2026-04-10 14:01:22 -04:00
|
|
|
}))
|
2025-10-27 07:20:23 +00:00
|
|
|
}
|