lavina/crates/projection-xmpp/src/presence.rs

210 lines
7.5 KiB
Rust
Raw Normal View History

//! Handling of all client2server presence stanzas
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
use anyhow::Result;
use quick_xml::events::Event;
use lavina_core::room::RoomId;
2024-05-18 18:18:42 +00:00
use proto_xmpp::bind::{Jid, Name, Resource, Server};
use proto_xmpp::client::Presence;
2024-05-18 18:18:42 +00:00
use proto_xmpp::muc::{Delay, HistoryMessage, XUser};
use proto_xmpp::xml::{Ignore, ToXml};
use crate::XmppConnection;
impl<'a> XmppConnection<'a> {
pub async fn handle_presence(&mut self, output: &mut Vec<Event<'static>>, p: Presence<Ignore>) -> Result<()> {
match p.to {
None => {
self.self_presence(output, p.r#type.as_deref()).await;
}
Some(Jid {
name: Some(name),
server,
// resources in MUCs are members' personas not implemented (yet?)
resource: Some(_),
}) if server.0 == self.hostname_rooms => {
2024-05-18 18:18:42 +00:00
let muc_join_response = self.muc_presence(&name).await?;
muc_join_response.serialize(output);
let history_on_join_response = self.send_history_on_join().await?;
history_on_join_response.serialize(output)
}
_ => {
// TODO other presence cases
let response = Presence::<()>::default();
response.serialize(output);
}
}
Ok(())
}
async fn self_presence(&mut self, output: &mut Vec<Event<'static>>, r#type: Option<&str>) {
match r#type {
Some("unavailable") => {
// do not print anything
}
None => {
let response = Presence::<()> {
to: Some(Jid {
name: Some(self.user.xmpp_name.clone()),
server: Server(self.hostname.clone()),
resource: Some(self.user.xmpp_resource.clone()),
}),
from: Some(Jid {
name: Some(self.user.xmpp_name.clone()),
server: Server(self.hostname.clone()),
resource: Some(self.user.xmpp_resource.clone()),
}),
..Default::default()
};
response.serialize(output);
}
_ => todo!(),
}
}
async fn muc_presence(&mut self, name: &Name) -> Result<(Presence<XUser>)> {
let a = self.user_handle.join_room(RoomId::from(name.0.clone())?).await?;
// TODO handle bans
let response = Presence {
to: Some(Jid {
name: Some(self.user.xmpp_name.clone()),
server: Server(self.hostname.clone()),
resource: Some(self.user.xmpp_resource.clone()),
}),
from: Some(Jid {
name: Some(name.clone()),
server: Server(self.hostname_rooms.clone()),
resource: Some(self.user.xmpp_muc_name.clone()),
}),
custom: vec![XUser],
..Default::default()
};
Ok(response)
}
2024-05-18 18:18:42 +00:00
async fn send_history_on_join(&self) -> Result<(HistoryMessage)> {
Ok(HistoryMessage {
id: "kek".to_string(),
to: Jid {
2024-05-18 18:35:31 +00:00
name: Some(Name("sauer@oflor.me".into())),
2024-05-18 18:18:42 +00:00
server: Server("localhost".into()),
resource: Some(Resource("tester".into())),
},
from: Jid {
2024-05-18 18:35:31 +00:00
name: Some(Name("pepe".into())),
server: Server("rooms.localhost".into()),
resource: Some(Resource("sauer".into())),
2024-05-18 18:18:42 +00:00
},
delay: Delay::new(
Jid {
2024-05-18 18:35:31 +00:00
name: Some(Name("pepe".into())),
server: Server("rooms.localhost".into()),
2024-05-18 18:18:42 +00:00
resource: Some(Resource("tester".into())),
},
"2021-10-10T10:10:10Z".to_string(),
),
body: "Vasya Pupkin says hello.".to_string(),
})
}
}
#[cfg(test)]
mod tests {
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
use anyhow::Result;
2024-05-18 18:18:42 +00:00
use lavina_core::player::PlayerId;
use proto_xmpp::bind::{Jid, Name, Resource, Server};
use proto_xmpp::client::Presence;
use proto_xmpp::muc::XUser;
2024-05-18 18:18:42 +00:00
use crate::testkit::{expect_user_authenticated, TestServer};
use crate::Authenticated;
#[tokio::test]
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
async fn test_muc_joining() -> Result<()> {
let server = TestServer::start().await.unwrap();
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
server.core.create_player(&PlayerId::from("tester")?).await?;
let player_id = PlayerId::from("tester").unwrap();
let user = Authenticated {
player_id,
xmpp_name: Name("tester".into()),
xmpp_resource: Resource("tester".into()),
xmpp_muc_name: Resource("tester".into()),
};
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let mut player_conn = server.core.connect_to_player(&user.player_id).await;
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap();
let response = conn.muc_presence(&user.xmpp_name).await.unwrap();
let expected = Presence {
to: Some(Jid {
name: Some(conn.user.xmpp_name.clone()),
server: Server(conn.hostname.clone()),
resource: Some(conn.user.xmpp_resource.clone()),
}),
from: Some(Jid {
name: Some(user.xmpp_name.clone()),
server: Server(conn.hostname_rooms.clone()),
resource: Some(conn.user.xmpp_muc_name.clone()),
}),
custom: vec![XUser],
..Default::default()
};
assert_eq!(expected, response);
server.shutdown().await.unwrap();
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
Ok(())
}
// Test that joining a room second time after a server restart,
// i.e. in-memory cache of memberships is cleaned, does not cause any issues.
#[tokio::test]
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
async fn test_muc_joining_twice() -> Result<()> {
let server = TestServer::start().await.unwrap();
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
server.core.create_player(&PlayerId::from("tester")?).await?;
let player_id = PlayerId::from("tester").unwrap();
let user = Authenticated {
player_id,
xmpp_name: Name("tester".into()),
xmpp_resource: Resource("tester".into()),
xmpp_muc_name: Resource("tester".into()),
};
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let mut player_conn = server.core.connect_to_player(&user.player_id).await;
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap();
let response = conn.muc_presence(&user.xmpp_name).await.unwrap();
let expected = Presence {
to: Some(Jid {
name: Some(conn.user.xmpp_name.clone()),
server: Server(conn.hostname.clone()),
resource: Some(conn.user.xmpp_resource.clone()),
}),
from: Some(Jid {
name: Some(user.xmpp_name.clone()),
server: Server(conn.hostname_rooms.clone()),
resource: Some(conn.user.xmpp_muc_name.clone()),
}),
custom: vec![XUser],
..Default::default()
};
assert_eq!(expected, response);
drop(conn);
let server = server.reboot().await.unwrap();
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let mut player_conn = server.core.connect_to_player(&user.player_id).await;
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap();
let response = conn.muc_presence(&user.xmpp_name).await.unwrap();
assert_eq!(expected, response);
server.shutdown().await.unwrap();
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
Ok(())
}
}