From 0cd0ed3c0d86f030c88088faee6a3194e23e52d8 Mon Sep 17 00:00:00 2001 From: phil Date: Sat, 11 Nov 2023 16:50:37 +0200 Subject: [PATCH] Adds token refresh to the fetcher --- src/apps/binary_upload.rs | 2 +- src/apps/code_editor.rs | 2 +- src/apps/login_app.rs | 4 +- src/apps/scoreboard_app.rs | 139 +++++++++++++++---------------------- src/code_editor/editor.rs | 2 +- src/helpers/fetchers.rs | 42 ++++++----- src/helpers/refresh.rs | 5 +- 7 files changed, 85 insertions(+), 111 deletions(-) diff --git a/src/apps/binary_upload.rs b/src/apps/binary_upload.rs index c00e5d7..34b248b 100644 --- a/src/apps/binary_upload.rs +++ b/src/apps/binary_upload.rs @@ -130,7 +130,7 @@ impl super::App for BinaryUpload { match submission { SubmissionResult::NotStarted => {} SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(&self.url); + self.token_refresh_promise = refresh::submit_refresh(); self.last_result = submission; } _ => { diff --git a/src/apps/code_editor.rs b/src/apps/code_editor.rs index e08eeac..dd71dc4 100644 --- a/src/apps/code_editor.rs +++ b/src/apps/code_editor.rs @@ -129,7 +129,7 @@ impl super::App for CodeEditor { match submission { SubmissionResult::NotStarted => {} SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(&self.url); + self.token_refresh_promise = refresh::submit_refresh(); self.last_result = submission; } _ => { diff --git a/src/apps/login_app.rs b/src/apps/login_app.rs index 2f88340..d1b7dc9 100644 --- a/src/apps/login_app.rs +++ b/src/apps/login_app.rs @@ -107,7 +107,7 @@ impl Default for LoginApp { .unwrap_or("http://localhost:3000/") .to_string(); Self { - token_refresh_promise: refresh::submit_refresh(&url), + token_refresh_promise: refresh::submit_refresh(), login_promise: Default::default(), register_promise: Default::default(), url, @@ -261,7 +261,7 @@ impl LoginApp { } Ok(LoginState::Expired) => { self.state = LoginState::Expired; - self.token_refresh_promise = refresh::submit_refresh(&self.url); + self.token_refresh_promise = refresh::submit_refresh(); } Err(e) => { self.toasts diff --git a/src/apps/scoreboard_app.rs b/src/apps/scoreboard_app.rs index 9883ff4..754102e 100644 --- a/src/apps/scoreboard_app.rs +++ b/src/apps/scoreboard_app.rs @@ -1,11 +1,11 @@ -use crate::helpers::{refresh, Challenges}; -use gloo_net::http; -use poll_promise::Promise; +use crate::helpers::{ + fetchers::{GetStatus, Getter}, + Challenges, +}; use scoreboard_db::Builder as FilterBuilder; use scoreboard_db::Filter as ScoreBoardFilter; use scoreboard_db::{NiceTime, Score, ScoreBoard, SortColumn}; -use std::str::FromStr; -use web_sys::RequestCredentials; +use std::{borrow::BorrowMut, str::FromStr}; #[derive(PartialEq, Clone, Copy, serde::Deserialize, serde::Serialize)] enum FilterOption { @@ -32,15 +32,11 @@ pub struct ScoreBoardApp { active_sort_column: String, scores: Option>, - - #[serde(skip)] - promise: Option>, - #[serde(skip)] - token_refresh_promise: refresh::RefreshPromise, #[serde(skip)] url: String, + #[serde(skip)] - refresh: bool, + score_fetcher: Option, } impl Default for ScoreBoardApp { @@ -49,63 +45,32 @@ impl Default for ScoreBoardApp { challenge: Challenges::default(), filter: FilterOption::All, sort_column: "time".to_string(), - promise: None, - token_refresh_promise: None, url: option_env!("BACKEND_URL") .unwrap_or("http://123.4.5.6:3000/") .to_string(), - refresh: true, - active_challenge: Challenges::default(), + active_challenge: Challenges::None, active_filter: FilterOption::All, active_sort_column: "time".to_string(), scores: None, + score_fetcher: None, } } } impl ScoreBoardApp { - fn fetch(&mut self, ctx: &egui::Context) { - if !self.refresh { - return; - } - self.refresh = false; + fn fetch(&mut self) { self.scores = None; let url = format!("{}api/game/scores/{}", self.url, self.challenge); - let ctx = ctx.clone(); - - let promise = poll_promise::Promise::spawn_local(async move { - let response = http::Request::get(&url).credentials(RequestCredentials::Include); - let response = response.send().await.unwrap(); - let text = response.text().await; - let text = text.map(|text| text.to_owned()); - let result = match response.status() { - 200 => { - let scores: Vec = serde_json::from_str(text.as_ref().unwrap()).unwrap(); - FetchResponse::Success(scores) - } - 401 => { - let text = match text { - Ok(text) => text, - Err(e) => e.to_string(), - }; - log::warn!("Auth Error: {:?}", text); - FetchResponse::FailAuth - } - _ => { - log::error!("Response: {:?}", text); - FetchResponse::Failure(text.unwrap()) - } - }; - ctx.request_repaint(); // wake up UI thread - result - }); - self.promise = Some(promise); + log::debug!("Fetching challenge info"); + let mut getter = Getter::new(&url, true); + getter.get(); + self.score_fetcher = Some(getter); } - fn check_for_reload(&mut self) { + fn check_for_reload(&mut self) -> bool { if self.active_challenge != self.challenge || self.active_filter != self.filter || self.active_sort_column != self.sort_column @@ -113,22 +78,29 @@ impl ScoreBoardApp { self.active_challenge = self.challenge; self.active_filter = self.filter; self.active_sort_column = self.sort_column.clone(); - self.refresh = true; + return true; } + false } - fn check_fetch_promise(&mut self) -> Option { - if let Some(promise) = &self.promise { - if let Some(result) = promise.ready() { - if let FetchResponse::FailAuth = result { - self.token_refresh_promise = refresh::submit_refresh(&self.url); + fn check_fetch_promise(&mut self) -> GetStatus { + let getter = &mut self.score_fetcher; + + if let Some(getter) = getter { + let result = &getter.check_promise(); + + match result { + GetStatus::Success(_) => { + self.score_fetcher = None; + } + GetStatus::Failed(_) => { + self.score_fetcher = None; } - let result = Some(result.clone()); - self.promise = None; - return result; + _ => {} } + return result.clone(); } - None + GetStatus::NotStarted } } @@ -138,8 +110,17 @@ impl super::App for ScoreBoardApp { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - self.check_for_reload(); - self.fetch(ctx); + if self.check_for_reload() { + self.fetch(); + } + + if let Some(fetcher) = self.score_fetcher.borrow_mut() { + if fetcher.refresh_context() { + log::debug!("Refreshing context"); + ctx.request_repaint(); + } + } + egui::Window::new(self.name()) .open(open) .default_width(400.0) @@ -192,7 +173,7 @@ impl super::View for ScoreBoardApp { ); ui.separator(); if ui.button("Refresh").clicked() { - self.refresh = true; + self.fetch(); } }); }); @@ -210,29 +191,21 @@ impl ScoreBoardApp { fn table_ui(&mut self, ui: &mut egui::Ui) { use egui_extras::{Column, TableBuilder}; - if let Some(result) = self.check_fetch_promise() { - match result { - FetchResponse::Success(s) => { - self.scores = Some(s); - } - FetchResponse::Failure(text) => { - ui.label(text); - } - FetchResponse::FailAuth => { - ui.label("Failed to authenticate, refreshing token"); - } + match self.check_fetch_promise() { + GetStatus::Success(text) => { + self.score_fetcher = None; + self.scores = Some(serde_json::from_str(&text).unwrap()); } - } - match refresh::check_refresh_promise(&mut self.token_refresh_promise) { - refresh::RefreshStatus::NotStarted => {} - refresh::RefreshStatus::InProgress => {} - refresh::RefreshStatus::Success => { - self.refresh = true; + GetStatus::Failed(e) => { + self.score_fetcher = None; + let message = format!("Failed to fetch scores: {}", e); + log::error!("{}", message); + ui.label(message); } - refresh::RefreshStatus::Failed(text) => { - log::error!("Failed to refresh token: {:?}", text); - ui.label(text); + GetStatus::InProgress => { + ui.label("Fetching scores..."); } + GetStatus::NotStarted => {} } let text_height = egui::TextStyle::Body.resolve(ui.style()).size; diff --git a/src/code_editor/editor.rs b/src/code_editor/editor.rs index 2ea2874..e34670e 100644 --- a/src/code_editor/editor.rs +++ b/src/code_editor/editor.rs @@ -166,7 +166,7 @@ impl CodeEditor { match submission { SubmissionResult::NotStarted => {} SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(&self.url); + self.token_refresh_promise = refresh::submit_refresh(); self.last_result = submission; } SubmissionResult::Success { score: _, message } => { diff --git a/src/helpers/fetchers.rs b/src/helpers/fetchers.rs index 92512d8..d1b74c3 100644 --- a/src/helpers/fetchers.rs +++ b/src/helpers/fetchers.rs @@ -1,3 +1,4 @@ +use crate::helpers::refresh; use gloo_net::http; use poll_promise::Promise; use web_sys::RequestCredentials; @@ -33,6 +34,7 @@ pub struct Getter { url: String, retry_count: usize, state_has_changed: bool, + token_refresh_promise: refresh::RefreshPromise, } impl Getter { @@ -46,10 +48,25 @@ impl Getter { false => 0, }, state_has_changed: false, + token_refresh_promise: None, } } pub fn check_promise(&mut self) -> GetStatus { + match refresh::check_refresh_promise(&mut self.token_refresh_promise) { + refresh::RefreshStatus::NotStarted => {} + refresh::RefreshStatus::InProgress => {} + refresh::RefreshStatus::Success => { + log::debug!("Retrying Request"); + self.get(); + return GetStatus::InProgress; + } + refresh::RefreshStatus::Failed(_) => { + self.state_has_changed = true; + return GetStatus::Failed("Failed to authenticate".to_string()); + } + } + let mut res = GetStatus::NotStarted; if let Some(promise) = &self.promise { res = match promise.ready() { @@ -58,8 +75,10 @@ impl Getter { Ok(FetchResponse::Failure(e)) => GetStatus::Failed(e.to_string()), Ok(FetchResponse::FailAuth) => { if self.retry_count > 0 { + log::debug!("Retrying auth"); self.retry_count -= 1; - self.get(); + self.promise = None; + self.token_refresh_promise = refresh::submit_refresh(); GetStatus::InProgress } else { GetStatus::Failed("Authentication failed".to_string()) @@ -115,24 +134,3 @@ impl Getter { self.promise = Some(promise); } } - -// impl Getter { -// pub fn _get_json(&mut self) { -// let url = self.url.clone(); -// let ctx = self.context.clone(); -// let with_credentials = self.with_credentials; -// let promise = Promise::spawn_local(async move { -// let request = http::Request::get(&url); -// let request = match with_credentials { -// true => request.credentials(RequestCredentials::Include), -// false => request, -// }; -// let response = request.send().await.map_err(|e| e.to_string())?; -// if let Some(ctx) = ctx { -// ctx.request_repaint(); // wake up UI thread -// } -// response.json::().await.map_err(|e| e.to_string()) -// }); -// self.promise = Some(promise); -// } -// } diff --git a/src/helpers/refresh.rs b/src/helpers/refresh.rs index 2a35568..85a2b55 100644 --- a/src/helpers/refresh.rs +++ b/src/helpers/refresh.rs @@ -16,7 +16,10 @@ pub enum RefreshStatus { pub type RefreshPromise = Option>>; -pub fn submit_refresh(url: &str) -> RefreshPromise { +pub fn submit_refresh() -> RefreshPromise { + let url = option_env!("BACKEND_URL") + .unwrap_or("http://123.4.5.6:3000/") + .to_string(); let url = format!("{}api/auth/refresh", url); log::debug!("Refreshing token");