bazzar/crates/web/src/session.rs

175 lines
6.1 KiB
Rust

use chrono::{NaiveDateTime, TimeZone};
use model::{AccessTokenString, RefreshTokenString};
use seed::prelude::*;
use crate::pages::AdminPage;
use crate::shared::notification::NotificationMsg;
use crate::{Model, Msg, Page, PublicPage};
#[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| {
chrono::DateTime::parse_from_rfc3339(&s)
.ok()
.map(|t| t.naive_utc())
});
redirect_on_session(model, orders);
}
pub fn redirect_on_session(model: &Model, orders: &mut impl Orders<Msg>) {
// seed::log!(&model.page, model.shared.me.is_some());
match &model.page {
Page::Admin(admin) => match admin {
AdminPage::Landing(_) => {}
AdminPage::Dashboard => {}
AdminPage::Products => {}
AdminPage::Product => {}
},
Page::Public(public) if model.shared.me.is_some() => match public {
PublicPage::SignUp(_) | PublicPage::SignIn(_) => {
let url = model.url.clone().set_path(&[] as &[&str]);
url.go_and_push();
orders.send_msg(crate::Msg::UrlChanged(subs::UrlChanged(url)));
orders.force_render_now();
}
_ => {}
},
Page::Public(public) => match public {
PublicPage::Listing(_) => {}
PublicPage::Product(_) => {}
PublicPage::SignIn(_) => {}
PublicPage::SignUp(_) => {}
PublicPage::ShoppingCart(_) => {}
PublicPage::Checkout(_) => {}
},
}
}
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,
role: _,
})) => {
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::SharedMsg::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) => {
seed::error!("request error", e);
}
FetchError::StatusError(status) => match status.code {
401 | 403 => {
orders
.skip()
.send_msg(Msg::Session(SessionMsg::SessionExpired));
}
_ => {
orders
.skip()
.send_msg(Msg::Shared(crate::shared::SharedMsg::Notification(
NotificationMsg::Error("Request failed".into()),
)));
}
},
},
}
}