Integrate Vantage live map for guild claims [skip ci]
This commit is contained in:
1 parent
52de8c3031
commit
602e7fd0f0
17 files changed
+470
-21
No files matched your search
@@ -538,7 +538,58 @@ pub async fn get_public_servers(state: State<'_, AppState>) -> Res<serde_json::V
|
||||
if !resp.status().is_success() {
|
||||
return Err("unable to load servers".into());
|
||||
}
|
||||
resp.json().await.map_err(err)
|
||||
let data: serde_json::Value = resp.json().await.map_err(err)?;
|
||||
let urls = data["servers"].as_array().into_iter().flatten().filter_map(|s| {
|
||||
Some((s["id"].as_i64()?, s["map_url"].as_str()?.to_owned()))
|
||||
}).collect();
|
||||
*state.map_urls.lock().unwrap() = urls;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MapResource {
|
||||
status: u16,
|
||||
data: Option<String>,
|
||||
etag: Option<String>,
|
||||
}
|
||||
|
||||
/// Native transport for Vantage assets. Browser WebViews cannot fetch a
|
||||
/// cross-origin Vantage live server, so the launcher uses its HTTP client.
|
||||
/// 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)?;
|
||||
let root = base.join(".").map_err(err)?;
|
||||
if !matches!(target.scheme(), "http" | "https") || target.origin() != base.origin()
|
||||
|| !target.path().starts_with(root.path()) || target.username() != ""
|
||||
|| target.password().is_some() || target.fragment().is_some() {
|
||||
return Err("map asset is outside the configured Vantage world".into());
|
||||
}
|
||||
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))
|
||||
.build().map_err(err)?;
|
||||
let mut request = client.get(target);
|
||||
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);
|
||||
}
|
||||
let response = request.send().await.map_err(err)?;
|
||||
let status = response.status().as_u16();
|
||||
let etag = response.headers().get(reqwest::header::ETAG).and_then(|v| v.to_str().ok()).map(str::to_owned);
|
||||
if status == 304 { return Ok(MapResource { status, data: None, etag }); }
|
||||
if !response.status().is_success() { return Ok(MapResource { status, data: None, etag }); }
|
||||
if response.content_length().is_some_and(|n| n > 64 * 1024 * 1024) {
|
||||
return Err("Vantage asset exceeds 64 MiB".into());
|
||||
}
|
||||
let bytes = response.bytes().await.map_err(err)?;
|
||||
if bytes.len() > 64 * 1024 * 1024 { return Err("Vantage asset exceeds 64 MiB".into()); }
|
||||
Ok(MapResource { status, data: Some(base64::engine::general_purpose::STANDARD.encode(bytes)), etag })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -85,6 +85,7 @@ pub fn run() {
|
||||
commands::get_player_stats,
|
||||
commands::get_leaderboard,
|
||||
commands::get_public_servers,
|
||||
commands::fetch_vantage_resource,
|
||||
commands::get_my_level,
|
||||
commands::get_my_quests,
|
||||
commands::claim_quest,
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::settings::Settings;
|
||||
use scopenet_core::Layout;
|
||||
use scopenet_shared::LauncherManifest;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, RwLock};
|
||||
|
||||
/// Values baked in at build time by CI (see `.github/workflows/release.yml`).
|
||||
@@ -34,6 +35,8 @@ pub struct AppState {
|
||||
pub accounts: RwLock<AccountsFile>,
|
||||
pub secrets: Secrets,
|
||||
pub manifest: RwLock<Option<LauncherManifest>>,
|
||||
/// Vantage URLs received from the panel, keyed by linked game server.
|
||||
pub map_urls: Mutex<HashMap<i64, String>>,
|
||||
/// Install/launch pipeline in progress (abortable).
|
||||
pub task: Mutex<Option<tokio::task::AbortHandle>>,
|
||||
pub game: Mutex<Vec<RunningGame>>,
|
||||
@@ -52,6 +55,7 @@ impl AppState {
|
||||
settings: RwLock::new(settings),
|
||||
accounts: RwLock::new(accounts),
|
||||
manifest: RwLock::new(manifest),
|
||||
map_urls: Mutex::new(HashMap::new()),
|
||||
task: Mutex::new(None),
|
||||
game: Mutex::new(Vec::new()),
|
||||
data_dir,
|
||||
|
||||
Reference in new issue
Block a user