diff --git a/router/src/components/form.rs b/router/src/components/form.rs index b128ac8306..98e90408a9 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -1,6 +1,6 @@ use crate::{ - hooks::has_router, use_navigate, use_resolved_path, NavigateOptions, - ToHref, Url, + hooks::has_router, resolve_redirect_url, use_navigate, use_resolved_path, + NavigateOptions, ToHref, Url, }; use leptos::{ html::form, @@ -447,8 +447,10 @@ where { let has_router = has_router(); if !has_router { - _ = server_fn::redirect::set_redirect_hook(|path: &str| { - _ = window().location().set_href(path); + _ = server_fn::redirect::set_redirect_hook(|loc: &str| { + if let Some(url) = resolve_redirect_url(loc) { + _ = window().location().set_href(&url.href()); + } }); } let action_url = if let Some(url) = action.url() { @@ -545,8 +547,10 @@ where { let has_router = has_router(); if !has_router { - _ = server_fn::redirect::set_redirect_hook(|path: &str| { - _ = window().location().set_href(path); + _ = server_fn::redirect::set_redirect_hook(|loc: &str| { + if let Some(url) = resolve_redirect_url(loc) { + _ = window().location().set_href(&url.href()); + } }); } let action_url = if let Some(url) = action.url() { diff --git a/router/src/components/router.rs b/router/src/components/router.rs index 55056a7b2a..5ec2e04c3f 100644 --- a/router/src/components/router.rs +++ b/router/src/components/router.rs @@ -1,7 +1,7 @@ use crate::{ - create_location, matching::resolve_path, scroll_to_el, use_location, - use_navigate, Branch, History, Location, LocationChange, RouteContext, - RouterIntegrationContext, State, + create_location, matching::resolve_path, resolve_redirect_url, + scroll_to_el, use_location, use_navigate, Branch, History, Location, + LocationChange, RouteContext, RouterIntegrationContext, State, }; #[cfg(not(feature = "ssr"))] use crate::{unescape, Url}; @@ -24,6 +24,7 @@ use std::{ use thiserror::Error; #[cfg(not(feature = "ssr"))] use wasm_bindgen::JsCast; +use wasm_bindgen::UnwrapThrowExt; static GLOBAL_ROUTERS_COUNT: AtomicUsize = AtomicUsize::new(0); @@ -56,15 +57,24 @@ pub fn Router( // set server function redirect hook let navigate = use_navigate(); let navigate = SendWrapper::new(navigate); - let router_hook = Box::new(move |path: &str| { - let path = path.to_string(); - // delay by a tick here, so that the Action updates *before* the redirect - request_animation_frame({ + let router_hook = Box::new(move |loc: &str| { + let Some(url) = resolve_redirect_url(loc) else { + return; // resolve_redirect_url() already logs an error + }; + let current_origin = + leptos_dom::helpers::location().origin().unwrap_throw(); + if url.origin() == current_origin { let navigate = navigate.clone(); - move || { - navigate(&path, Default::default()); - } - }); + // delay by a tick here, so that the Action updates *before* the redirect + request_animation_frame(move || { + navigate(&url.pathname(), Default::default()); + }); + // Use set_href() if the conditions for client-side navigation were not satisfied + } else if let Err(e) = + leptos_dom::helpers::location().set_href(&url.href()) + { + leptos::logging::error!("Failed to redirect: {e:#?}"); + } }) as RedirectHook; _ = server_fn::redirect::set_redirect_hook(router_hook); diff --git a/router/src/hooks.rs b/router/src/hooks.rs index d9a9fc1236..0c53689ac5 100644 --- a/router/src/hooks.rs +++ b/router/src/hooks.rs @@ -3,8 +3,8 @@ use crate::{ RouterContext, }; use leptos::{ - create_memo, request_animation_frame, signal_prelude::*, use_context, Memo, - Oco, + create_memo, request_animation_frame, signal_prelude::*, use_context, + window, Memo, Oco, }; use std::{rc::Rc, str::FromStr}; @@ -216,3 +216,28 @@ pub(crate) fn use_is_back_navigation() -> ReadSignal { let router = use_router(); router.inner.is_back.read_only() } + +/// Resolves a redirect location to an (absolute) URL. +pub(crate) fn resolve_redirect_url(loc: &str) -> Option { + let origin = match window().location().origin() { + Ok(origin) => origin, + Err(e) => { + leptos::logging::error!("Failed to get origin: {:#?}", e); + return None; + } + }; + + // TODO: Use server function's URL as base instead. + let base = origin; + + match web_sys::Url::new_with_base(loc, &base) { + Ok(url) => Some(url), + Err(e) => { + leptos::logging::error!( + "Invalid redirect location: {}", + e.as_string().unwrap_or_default(), + ); + None + } + } +} diff --git a/server_fn/src/redirect.rs b/server_fn/src/redirect.rs index aa279d3936..e20968a115 100644 --- a/server_fn/src/redirect.rs +++ b/server_fn/src/redirect.rs @@ -25,9 +25,9 @@ pub fn set_redirect_hook( REDIRECT_HOOK.set(Box::new(hook)) } -/// Calls the hook that has been set by [`set_redirect_hook`] to redirect to `path`. -pub fn call_redirect_hook(path: &str) { +/// Calls the hook that has been set by [`set_redirect_hook`] to redirect to `loc`. +pub fn call_redirect_hook(loc: &str) { if let Some(hook) = REDIRECT_HOOK.get() { - hook(path) + hook(loc) } }