bazzar/web/src/session.rs

144 lines
4.9 KiB
Rust
Raw Normal View History

2022-05-15 10:30:15 +02:00
use chrono::{NaiveDateTime, TimeZone};
use model::{AccessTokenString, RefreshTokenString};
use seed::prelude::*;
use crate::shared::notification::NotificationMsg;
use crate::{Model, Msg};
#[derive(Debug)]
pub enum SessionMsg {
SessionReceived {
access_token: AccessTokenString,
refresh_token: RefreshTokenString,
exp: NaiveDateTime,
},
SessionExpired,
RefreshToken(RefreshTokenString),
TokenRefreshed(crate::api::NetRes<model::api::SessionOutput>),
CheckSession,
}
pub fn init(model: &mut Model, orders: &mut impl Orders<Msg>) {
orders.stream(streams::interval(500, || {
Msg::Session(SessionMsg::CheckSession)
}));
model.shared.access_token = LocalStorage::get::<_, String>("at")
.ok()
.map(model::AccessTokenString::new);
model.shared.refresh_token = LocalStorage::get::<_, String>("rt")
.ok()
.map(model::RefreshTokenString::new);
model.shared.exp = LocalStorage::get::<_, String>("exp").ok().and_then(|s| {
seed::log!("Parsing ", s);
chrono::DateTime::parse_from_rfc3339(&s)
.ok()
.map(|t| t.naive_utc())
})
}
pub fn update(msg: SessionMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
SessionMsg::SessionReceived {
access_token,
refresh_token,
exp,
} => {
LocalStorage::insert("at", access_token.as_str()).ok();
LocalStorage::insert("rt", refresh_token.as_str()).ok();
let encoded = {
let l: chrono::DateTime<chrono::Local> =
chrono::Local.from_local_datetime(&exp).unwrap();
l.to_rfc3339()
};
LocalStorage::insert("exp", &encoded).ok();
model.shared.access_token = Some(access_token);
model.shared.refresh_token = Some(refresh_token);
model.shared.exp = Some(exp);
}
SessionMsg::SessionExpired => {
LocalStorage::remove("at").ok();
LocalStorage::remove("rt").ok();
LocalStorage::remove("exp").ok();
orders.force_render_now();
}
SessionMsg::CheckSession => {
orders.skip();
if model.shared.refresh_token.is_none() {
return;
}
if let Some(exp) = model.shared.exp {
if exp > chrono::Utc::now().naive_utc() - chrono::Duration::seconds(1) {
return;
}
if let Some(token) = model.shared.refresh_token.take() {
orders
.skip()
.send_msg(Msg::Session(SessionMsg::RefreshToken(token)));
}
}
}
SessionMsg::RefreshToken(token) => {
orders.skip().perform_cmd(async {
Msg::Session(SessionMsg::TokenRefreshed(
crate::api::public::refresh_token(token).await,
))
});
}
SessionMsg::TokenRefreshed(crate::api::NetRes::Success(model::api::SessionOutput {
access_token,
refresh_token,
exp,
})) => {
orders
.skip()
.send_msg(Msg::Session(SessionMsg::SessionReceived {
access_token,
refresh_token,
exp,
}));
}
SessionMsg::TokenRefreshed(crate::api::NetRes::Error(model::api::Failure { errors })) => {
errors.into_iter().for_each(|msg| {
orders
.skip()
.send_msg(Msg::Shared(crate::shared::Msg::Notification(
NotificationMsg::Error(msg),
)));
});
}
SessionMsg::TokenRefreshed(crate::api::NetRes::Http(e)) => match e {
FetchError::JsonError(json) => {
seed::error!("invalid data in response", json);
}
FetchError::DomException(dom) => {
seed::error!("dom", dom);
}
FetchError::PromiseError(res) => {
seed::error!("promise", res);
}
FetchError::NetworkError(net) => {
seed::error!("net", net);
}
FetchError::RequestError(e) => {
2022-05-15 11:38:35 +02:00
seed::error!(e);
2022-05-15 10:30:15 +02:00
}
FetchError::StatusError(status) => match status.code {
401 | 403 => {
orders
.skip()
.send_msg(Msg::Session(SessionMsg::SessionExpired));
}
_ => {
orders
.skip()
.send_msg(Msg::Shared(crate::shared::Msg::Notification(
NotificationMsg::Error("Request failed".into()),
)));
}
},
},
}
}