The SPA now sends the user to a new /login page whenever an API call answers 401, remembering the page they were on. The OIDC callback always lands back on the configured post-login redirect, so the saved page is restored client-side once a session exists. - auth: save/restore the return path in sessionStorage; defer the navigation so the failing request can finish first - api: centralize 401 handling (me() still treats 401 as logged out) - login page + shared login card, extracted from the lobby - guard async signal writes so an in-flight request cannot touch a page unmounted by the redirect
96 lines
3.1 KiB
Rust
96 lines
3.1 KiB
Rust
//! Application entry point.
|
|
mod api;
|
|
mod auth;
|
|
mod components;
|
|
mod model;
|
|
mod pages;
|
|
mod ws;
|
|
|
|
use sycamore::prelude::*;
|
|
use sycamore_router::{navigate, HistoryIntegration, Route, Router};
|
|
use wasm_bindgen_futures::spawn_local;
|
|
|
|
use pages::game::GamePage;
|
|
use pages::history::HistoryPage;
|
|
use pages::leaderboard::LeaderboardPage;
|
|
use pages::lobby::LobbyPage;
|
|
use pages::login::LoginPage;
|
|
|
|
#[derive(Route, Clone)]
|
|
enum AppRoutes {
|
|
#[to("/")]
|
|
Lobby,
|
|
#[to("/login")]
|
|
Login,
|
|
#[to("/game/<id>")]
|
|
Game { id: String },
|
|
#[to("/history")]
|
|
History,
|
|
#[to("/leaderboard")]
|
|
Leaderboard,
|
|
#[not_found]
|
|
NotFound,
|
|
}
|
|
|
|
/// After the OIDC callback lands the SPA back on the configured
|
|
/// post-login redirect, forward the user to the page they were on before
|
|
/// being sent to the login page (see [`auth`]) — but only once a session
|
|
/// exists, so a cancelled login never bounces into a stale page.
|
|
fn restore_page_after_login() {
|
|
let Some(path) = auth::saved_return_to() else {
|
|
return;
|
|
};
|
|
spawn_local(async move {
|
|
match api::me().await {
|
|
Ok(Some(_)) => {
|
|
auth::clear_return_to();
|
|
if auth::current_path() != path {
|
|
navigate(&path);
|
|
}
|
|
}
|
|
// Not logged in: keep the target for the next successful
|
|
// login (a reload of the login page must not lose it).
|
|
Ok(None) => {}
|
|
// The probe failed (offline?): keep the target, too.
|
|
Err(_) => {}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn main() {
|
|
console_error_panic_hook::set_once();
|
|
sycamore::render(|| {
|
|
view! {
|
|
Router(
|
|
integration=HistoryIntegration::new(),
|
|
view=|route: ReadSignal<AppRoutes>| {
|
|
view! {
|
|
div(class="app") {
|
|
(match route.get_clone() {
|
|
AppRoutes::Lobby => view! { LobbyPage() },
|
|
AppRoutes::Login => view! { LoginPage() },
|
|
AppRoutes::Game { id } => view! { GamePage(id=id) },
|
|
AppRoutes::History => view! { HistoryPage() },
|
|
AppRoutes::Leaderboard => view! { LeaderboardPage() },
|
|
AppRoutes::NotFound => view! {
|
|
div(class="status-card") {
|
|
div(class="card") {
|
|
div(class="card-body p-4") {
|
|
h1(class="h4 mb-3") { "Page not found" }
|
|
a(class="btn btn-warning", href="/") {
|
|
"Back to lobby"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
});
|
|
restore_page_after_login();
|
|
}
|