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), CheckSession, } pub fn init(model: &mut Model, orders: &mut impl Orders) { 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) { // 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) { 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.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()), ))); } }, }, } }