···1515//! forwarded with their real status + body.
16161717pub mod error;
1818+pub mod ssr;
1819pub mod state;
1920pub mod views;
2021···106107 ))
107108 // getSession takes no parameters, so it bypasses ExtractXrpc (which
108109 // decodes the query string; a unit-struct request from an empty query is
109109- // rejected by serde_html_form). Identity comes from the session.
110110+ // rejected by serde_html_form). It uses Jacquard's API/headless optional
111111+ // extractor so the documented x-jacquard-session fallback is confined to
112112+ // this session bridge surface; other public read endpoints keep the
113113+ // browser-oriented optional extractor semantics.
110114 .route(
111115 GetSessionRequest::PATH,
112116 axum::routing::get(actor::get_session),
+70
src/appview/ssr.rs
···11+//! Server-side bridge for SSR and Dioxus server functions.
22+//!
33+//! Normal SSR/server-function reads should extract the inbound OAuth session
44+//! from the current Axum request and call appview helpers directly in-process.
55+//! The `x-jacquard-session` utility below is only an escape hatch for the rare
66+//! server-side outbound XRPC call that cannot be avoided.
77+88+use axum::extract::FromRequestParts;
99+use dioxus::prelude::{ServerFnError, dioxus_fullstack::FullstackContext};
1010+use http::HeaderValue;
1111+use jacquard::identity::PublicResolver;
1212+use jacquard::oauth::client::OAuthSession;
1313+use jacquard_axum::oauth::ExtractOptionalOAuthSession;
1414+use jacquard_common::session::SessionKey;
1515+use jacquard_common::xrpc::CallOptions;
1616+use polymodel_api::space_polymodel::actor::get_session::GetSessionOutput;
1717+1818+use super::actor;
1919+use super::state::AppState;
2020+use crate::oauth::SqliteAuthStore;
2121+2222+pub type PolymodelOAuthSession = OAuthSession<PublicResolver, SqliteAuthStore>;
2323+2424+/// Extract the current request's optional OAuth session and resolve the
2525+/// `getSession` appview output without issuing a same-origin XRPC request.
2626+pub async fn get_session_output_from_fullstack_context() -> Result<GetSessionOutput, ServerFnError>
2727+{
2828+ let context = FullstackContext::current()
2929+ .ok_or_else(|| ServerFnError::new("missing Dioxus fullstack request context"))?;
3030+ let state = context
3131+ .extension::<AppState>()
3232+ .ok_or_else(|| ServerFnError::new("missing Polymodel AppState request extension"))?;
3333+ let session = extract_optional_oauth_session(&context, &state).await?;
3434+ actor::get_session_output(&state, session.as_ref())
3535+ .await
3636+ .map_err(|error| ServerFnError::new(format!("{error:?}")))
3737+}
3838+3939+async fn extract_optional_oauth_session(
4040+ context: &FullstackContext,
4141+ state: &AppState,
4242+) -> Result<Option<PolymodelOAuthSession>, ServerFnError> {
4343+ let mut parts = context.parts_mut().clone();
4444+ ExtractOptionalOAuthSession::<PublicResolver, SqliteAuthStore>::from_request_parts(
4545+ &mut parts, state,
4646+ )
4747+ .await
4848+ .map(|ExtractOptionalOAuthSession(session)| session)
4949+ .map_err(|error| ServerFnError::new(error.to_string()))
5050+}
5151+5252+/// Build Jacquard call options carrying the encoded session-key header.
5353+///
5454+/// Use this only when a server-side outbound XRPC hop is unavoidable. Normal
5555+/// SSR/server-function appview reads should stay in-process via the helpers
5656+/// above.
5757+#[allow(dead_code)]
5858+pub fn session_key_call_options(
5959+ state: &AppState,
6060+ key: &SessionKey,
6161+) -> Result<CallOptions, ServerFnError> {
6262+ let encoded = jacquard_axum::oauth::encode_session_key(key)
6363+ .map_err(|error| ServerFnError::new(error.to_string()))?;
6464+ let value =
6565+ HeaderValue::from_str(&encoded).map_err(|error| ServerFnError::new(error.to_string()))?;
6666+ Ok(CallOptions {
6767+ extra_headers: vec![(state.oauth_config.session_header.clone(), value)],
6868+ ..CallOptions::default()
6969+ })
7070+}