diff --git a/crates/tauri-app/src-tauri/src/lib.rs b/crates/tauri-app/src-tauri/src/lib.rs index 0643205..5309827 100644 --- a/crates/tauri-app/src-tauri/src/lib.rs +++ b/crates/tauri-app/src-tauri/src/lib.rs @@ -543,8 +543,9 @@ pub fn run() { .unwrap_or_else(|_| "http://127.0.0.1:2584".to_string()); let state = AppState { - pds: PdsHttpClient::new(pds_url), - appview: AppViewClient::new(appview_url), + pds: PdsHttpClient::new(pds_url.clone()), + appview: AppViewClient::new(appview_url.clone()), + appview_url, store: store::SessionStore::new(), }; @@ -722,6 +723,7 @@ pub fn run() { open_external_url, profile_get_record, profile_set, + get_api_urls, ]) .run(tauri::generate_context!()) .expect("error while running maarcadetweet"); @@ -741,6 +743,32 @@ async fn profile_get_record( .map_err(|e| e.to_string()) } +/// Frontend-side base URLs the Tauri shell was started with. Used by +/// the Svelte components to build absolute fetch URLs — a relative +/// `/api/...` resolves against the Vite dev origin (port 1430), not +/// the AppView (port 2584), and the Vite server has no proxy +/// configured, so the fetch lands on a 404 HTML page and +/// `response.json()` throws `SyntaxError`. +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct ApiUrls { + pds_url: String, + appview_url: String, +} + +#[tauri::command] +fn get_api_urls(state: tauri::State<'_, AppState>) -> ApiUrls { + // Sync command — the URLs are immutable for the lifetime of the + // Tauri shell (read from MAARCADETWEET_*_URL at startup), so no + // async machinery is needed. Returns the AppView URL the + // frontend needs; PDS URL is exposed too so future fetch-based + // XRPC calls don't have to add their own command. + ApiUrls { + pds_url: state.pds.base_url.clone(), + appview_url: state.appview_url.clone(), + } +} + #[tauri::command] async fn profile_set( state: tauri::State<'_, AppState>, diff --git a/crates/tauri-app/src-tauri/src/state.rs b/crates/tauri-app/src-tauri/src/state.rs index 94b7bfc..aab1d63 100644 --- a/crates/tauri-app/src-tauri/src/state.rs +++ b/crates/tauri-app/src-tauri/src/state.rs @@ -1,5 +1,10 @@ pub struct AppState { pub pds: crate::pds_client::PdsHttpClient, pub appview: crate::appview_client::AppViewClient, + /// Base URL of the AppView service (`http://host:port`, no + /// trailing slash). Stored verbatim so the frontend can build + /// absolute URLs for fetch calls — a relative `/api/profile/…` + /// would resolve against the Vite dev origin, not the AppView. + pub appview_url: String, pub store: crate::store::SessionStore, } diff --git a/crates/tauri-app/src/lib/api/client.ts b/crates/tauri-app/src/lib/api/client.ts index 5fe25d9..e0310f1 100644 --- a/crates/tauri-app/src/lib/api/client.ts +++ b/crates/tauri-app/src/lib/api/client.ts @@ -24,6 +24,33 @@ async function tauriCall(cmd: string, fallback: T, args?: Record(cmd, args); } +/// Base URLs the Tauri shell was started with. Exposed via the +/// `get_api_urls` command so the Svelte components can build +/// absolute fetch URLs — a relative `/api/...` resolves against +/// the Vite dev origin (port 1430), not the AppView (port 2584), +/// and `response.json()` then throws `SyntaxError` on the 404 +/// HTML page. Cached after the first successful call. +let _apiUrlsCache: { pdsUrl: string; appviewUrl: string } | null = null; + +export type ApiUrls = { pdsUrl: string; appviewUrl: string }; + +/// Fetch the AppView + PDS base URLs from the Rust shell. Returns +/// the cached value on subsequent calls. +export async function getApiUrls(): Promise { + if (_apiUrlsCache) return _apiUrlsCache; + const urls = await safeInvoke("get_api_urls"); + _apiUrlsCache = urls; + return urls; +} + +/// Convenience: just the AppView base URL (the only one the UI +/// currently needs for direct fetch calls). Same caching as +/// `getApiUrls`. +export async function getAppviewUrl(): Promise { + const { appviewUrl } = await getApiUrls(); + return appviewUrl; +} + /** * Strict variant of `tauriCall` for actions that MUST hit the * Tauri runtime (login, register, logout, post, like, etc.). In diff --git a/crates/tauri-app/src/lib/components/ProfileView.svelte b/crates/tauri-app/src/lib/components/ProfileView.svelte index fa5db70..7f58c05 100644 --- a/crates/tauri-app/src/lib/components/ProfileView.svelte +++ b/crates/tauri-app/src/lib/components/ProfileView.svelte @@ -6,6 +6,7 @@ pickAndUploadImage, fetchBlob, releaseBlob, + getAppviewUrl, } from "../api/client"; import { onDestroy, onMount } from "svelte"; @@ -74,7 +75,12 @@ async function load() { viewModel = { kind: "loading" }; try { - const r = await fetch(`/api/profile/${encodeURIComponent(handle)}`); + // Absolute URL because the Tauri webview's origin is the Vite + // dev server (port 1430), not the AppView (port 2584) — a + // relative `/api/profile/…` would resolve against Vite, hit a + // 404 HTML page, and `r.json()` would throw `SyntaxError`. + const base = await getAppviewUrl(); + const r = await fetch(`${base}/api/profile/${encodeURIComponent(handle)}`); if (!r.ok) { viewModel = { kind: "error",