matrix_ui_serializable/init/
session.rs1use anyhow::anyhow;
2use serde::{Deserialize, Serialize};
3use tracing::{error, info};
4
5use matrix_sdk::{
6 AuthSession, Client,
7 authentication::{
8 matrix::MatrixSession,
9 oauth::{ClientId, OAuthSession, UserSession},
10 },
11};
12
13use std::sync::Arc;
14
15use crate::{
16 init::{
17 login::build_client,
18 singletons::{CLIENT, HAS_SESSION_STORED},
19 },
20 models::{
21 events::{ToastNotificationRequest, ToastNotificationVariant},
22 state_updater::StateUpdater,
23 },
24 room::notifications::enqueue_toast_notification,
25};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ClientSession {
30 pub(crate) homeserver: String,
32
33 pub(crate) db_identifier: String,
36
37 pub(crate) passphrase: String,
39}
40
41impl ClientSession {
42 pub fn new(homeserver: String, db_identifier: String, passphrase: String) -> Self {
43 ClientSession {
44 homeserver,
45 db_identifier,
46 passphrase,
47 }
48 }
49}
50
51#[derive(Debug, Serialize, Deserialize)]
52pub struct FullMatrixSession {
53 pub client_session: ClientSession,
54 pub user_session: SerializableAuthSession,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub enum SerializableAuthSession {
60 Matrix(MatrixSession),
62
63 OAuth(SerializableOAuthSession),
65}
66
67impl From<AuthSession> for SerializableAuthSession {
68 fn from(value: AuthSession) -> Self {
69 match value {
70 AuthSession::Matrix(m) => Self::Matrix(m),
71 AuthSession::OAuth(o) => Self::OAuth(o.into()),
72 _ => panic!("This type of auth is not yet supported"),
73 }
74 }
75}
76
77impl From<SerializableAuthSession> for AuthSession {
78 fn from(value: SerializableAuthSession) -> Self {
79 match value {
80 SerializableAuthSession::Matrix(m) => Self::Matrix(m),
81 SerializableAuthSession::OAuth(o) => Self::OAuth(Box::new(o.into())),
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct SerializableOAuthSession {
89 pub client_id: ClientId,
91
92 pub user: UserSession,
94}
95
96impl From<Box<OAuthSession>> for SerializableOAuthSession {
97 fn from(value: Box<OAuthSession>) -> Self {
98 Self {
99 client_id: value.client_id,
100 user: value.user,
101 }
102 }
103}
104
105impl From<SerializableOAuthSession> for OAuthSession {
106 fn from(value: SerializableOAuthSession) -> Self {
107 Self {
108 client_id: value.client_id,
109 user: value.user,
110 }
111 }
112}
113
114impl FullMatrixSession {
115 pub fn new(client_session: ClientSession, user_session: AuthSession) -> Self {
116 FullMatrixSession {
117 client_session,
118 user_session: user_session.into(),
119 }
120 }
121}
122
123pub async fn restore_client_from_session(session: FullMatrixSession) -> anyhow::Result<Client> {
124 let FullMatrixSession {
125 client_session,
126 user_session,
127 } = session;
128
129 let (client, _) = build_client(None, Some(client_session)).await?;
130
131 client.restore_session(user_session).await?;
132
133 CLIENT
134 .set(client.clone())
135 .expect("BUG: CLIENT already set!");
136
137 Ok(client)
138}
139
140pub async fn try_restore_session_to_state(
141 session_option: Option<String>,
142) -> crate::Result<Option<Client>> {
143 match session_option {
144 None => {
145 HAS_SESSION_STORED
146 .set(false)
147 .map_err(|b| anyhow!("HAS_SESSION_STORED was already defined. {b}"))?;
148 Ok(None)
149 }
150 Some(session_string) => {
151 HAS_SESSION_STORED
152 .set(true)
153 .map_err(|b| anyhow!("HAS_SESSION_STORED was already defined. {b}"))?;
154 let session: FullMatrixSession =
155 serde_json::from_str(&session_string).map_err(|e| anyhow!(e))?;
156 let initial_client = restore_client_from_session(session).await?;
157 Ok(Some(initial_client))
158 }
159 }
160}
161
162pub(crate) fn setup_token_background_save(updater: Arc<Box<dyn StateUpdater>>) {
167 tokio::spawn(async move {
168 let client = CLIENT.wait();
169 while let Ok(update) = client.subscribe_to_session_changes().recv().await {
170 match update {
171 matrix_sdk::SessionChange::UnknownToken(s) => {
172 enqueue_toast_notification(ToastNotificationRequest::new(
173 format!(
174 "This session is no longer valid. Soft logout: {}",
175 s.soft_logout
176 ),
177 None,
178 ToastNotificationVariant::Error,
179 ));
180 error!(
181 "Received an unknown token error; soft logout? {}",
182 s.soft_logout
183 );
184 }
185 matrix_sdk::SessionChange::TokensRefreshed => {
186 if let Err(err) = update_stored_session(client, updater.clone()).await {
188 enqueue_toast_notification(ToastNotificationRequest::new(
189 format!("Failed to persist refreshed session. Error: {err}"),
190 None,
191 ToastNotificationVariant::Error,
192 ));
193 error!("Unable to store a session in the background: {err}");
194 }
195 }
196 }
197 }
198 });
199}
200
201async fn update_stored_session(
206 client: &Client,
207 updater: Arc<Box<dyn StateUpdater>>,
208) -> anyhow::Result<()> {
209 info!("Updating the stored session...");
210
211 let user_session = client
212 .session()
213 .ok_or(anyhow!("No auth session available to persist!"))?;
214
215 updater
216 .as_ref()
217 .persist_refreshed_session(user_session)
218 .await?;
219
220 info!("Updating the stored session: done!");
221 Ok(())
222}