Live map: plugin/mod chunk+player sync, admin panel Live Map section, launcher transport

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjMbLQujBHunCCu5GpsHaT
This commit is contained in:
Claude committed 2026-09-30 17:15:24 +00:00
1 parent 8acb4a64e6
commit fdc54577fd
14 files changed
+769 -8

No files matched your search

+41 -2
View File
@@ -637,7 +637,6 @@ pub struct MapResource {
/// Only paths under a map URL supplied by the panel can be fetched.
#[tauri::command]
pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
use base64::Engine;
let configured = state.map_urls.lock().unwrap().get(&server_id).cloned().ok_or("map not configured for this server")?;
let base = reqwest::Url::parse(&configured).map_err(err)?;
let target = reqwest::Url::parse(&url).map_err(err)?;
@@ -651,15 +650,55 @@ pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64,
{
return Err("map asset is outside the configured Vantage world".into());
}
fetch_map_asset(target, etag, None).await
}
/// What the launcher needs to offer a Live Map button for one game server.
#[tauri::command]
pub async fn get_livemap_info(state: State<'_, AppState>, server_id: i64) -> Res<serde_json::Value> {
let req = account_api(&state, reqwest::Method::GET, &format!("/servers/{server_id}/livemap")).await?;
let resp = req.send().await.map_err(err)?;
if !resp.status().is_success() {
let body: serde_json::Value = resp.json().await.unwrap_or_default();
return Err(body["error"].as_str().unwrap_or("unable to load the live map").to_string());
}
resp.json().await.map_err(err)
}
/// Native transport for the panel-hosted live map. The session token is
/// attached here, so it never reaches page scripts, and only URLs under
/// this panel's `/api/livemap/<server>/` can be fetched.
#[tauri::command]
pub async fn fetch_livemap_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
let panel = state.panel_url().ok_or("no panel configured")?;
let token = accounts::panel_token(&state).ok_or("sign in to view the live map")?;
let base = reqwest::Url::parse(&format!("{}/api/livemap/{server_id}/", panel.trim_end_matches('/'))).map_err(err)?;
let target = reqwest::Url::parse(&url).map_err(err)?;
if target.origin() != base.origin()
|| !target.path().starts_with(base.path())
|| target.username() != ""
|| target.password().is_some()
|| target.fragment().is_some()
{
return Err("map asset is outside this server's live map".into());
}
fetch_map_asset(target, etag, Some(token)).await
}
async fn fetch_map_asset(target: reqwest::Url, etag: Option<String>, bearer: Option<String>) -> Res<MapResource> {
use base64::Engine;
if target.as_str().len() > 2048 {
return Err("map asset URL is too long".into());
}
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.timeout(std::time::Duration::from_secs(30))
.timeout(std::time::Duration::from_secs(60))
.build()
.map_err(err)?;
let mut request = client.get(target);
if let Some(token) = bearer {
request = request.bearer_auth(token);
}
if let Some(etag) = etag.filter(|e| e.len() < 256 && !e.contains('\n') && !e.contains('\r')) {
request = request.header(reqwest::header::IF_NONE_MATCH, etag);
}
+2
View File
@@ -92,6 +92,8 @@ pub fn run() {
commands::get_leaderboard,
commands::get_public_servers,
commands::fetch_vantage_resource,
commands::fetch_livemap_resource,
commands::get_livemap_info,
commands::get_my_level,
commands::get_my_quests,
commands::claim_quest,