OneLauncher-Cracked/packages/oneclient_net/src/config.rs
sakuyma 45a970c38a
Small QoL fixes: Java refresh, clipboard toasts, custom image endpoints (#852)
* fix(java): add refresh button for stale runtimes

* fix(notifications): keep clipboard toasts out of the notification center

* fix(images): allow custom endpoints on non-public hosts

* chore: make toast_only notifications not increment notification counter

* chore: reorder navbar links

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-18 23:43:49 +02:00

160 lines
4.9 KiB
Rust

use oneclient_common::constants;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NetConfig {
pub curseforge_api_key: String,
pub modrinth_api_key: Option<String>,
pub metadata_api_url: String,
/// Never has a trailing slash
pub meta_url_base: String,
}
impl Default for NetConfig {
fn default() -> Self {
Self {
curseforge_api_key: constants::CURSEFORGE_API_KEY.to_string(),
modrinth_api_key: None,
metadata_api_url: constants::METADATA_API_URL.to_string(),
meta_url_base: constants::META_URL_BASE.to_string(),
}
}
}
impl NetConfig {
/// Blank is treated as absent so clearing a field in the settings UI
/// restores the default rather than sending an empty key
#[must_use]
pub fn with_overrides(
mut self,
curseforge_api_key: Option<&str>,
modrinth_api_key: Option<&str>,
metadata_api_url: Option<&str>,
meta_url_base: Option<&str>,
) -> Self {
if let Some(value) = non_empty(curseforge_api_key) {
self.curseforge_api_key = value;
}
self.modrinth_api_key = non_empty(modrinth_api_key);
if let Some(value) = non_empty(metadata_api_url) {
self.metadata_api_url = value;
}
if let Some(value) = non_empty(meta_url_base) {
self.meta_url_base = value;
}
self.meta_url_base = self.meta_url_base.trim_end_matches('/').to_string();
self
}
#[must_use]
pub fn modrinth_headers(&self) -> Vec<(String, String)> {
match &self.modrinth_api_key {
Some(token) => vec![("Authorization".to_string(), token.clone())],
None => Vec::new(),
}
}
/// Whether `url` targets a host the user explicitly configured as the
/// custom API endpoint or meta URL base.
#[must_use]
pub fn allows_host(&self, url: &reqwest::Url) -> bool {
let Some(host) = url.host_str() else {
return false;
};
let port = url.port_or_known_default();
[self.metadata_api_url.as_str(), self.meta_url_base.as_str()]
.into_iter()
.filter_map(|base| base.parse::<reqwest::Url>().ok())
.any(|base| {
base.host_str() == Some(host) && base.port_or_known_default() == port
})
}
}
fn non_empty(value: Option<&str>) -> Option<String> {
value
.map(str::trim)
.filter(|v| !v.is_empty())
.map(str::to_string)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blank_overrides_fall_back_to_defaults() {
let config = NetConfig::default().with_overrides(Some(" "), Some(""), None, None);
assert_eq!(config.curseforge_api_key, constants::CURSEFORGE_API_KEY);
assert_eq!(config.modrinth_api_key, None);
assert_eq!(config.metadata_api_url, constants::METADATA_API_URL);
}
#[test]
fn meta_url_base_never_keeps_a_trailing_slash() {
let config = NetConfig::default().with_overrides(
None,
None,
None,
Some("https://example.test/base/"),
);
assert_eq!(config.meta_url_base, "https://example.test/base");
}
#[test]
fn overrides_replace_defaults() {
let config = NetConfig::default().with_overrides(
Some("key"),
Some("token"),
Some("https://a"),
None,
);
assert_eq!(config.curseforge_api_key, "key");
assert_eq!(
config.modrinth_headers(),
vec![("Authorization".to_string(), "token".to_string())]
);
assert_eq!(config.metadata_api_url, "https://a");
}
fn parsed(url: &str) -> reqwest::Url {
url.parse().unwrap()
}
#[test]
fn allows_host_matches_a_configured_local_endpoint() {
let config = NetConfig::default().with_overrides(None, None, None, Some("http://localhost:8000"));
assert!(config.allows_host(&parsed("http://localhost:8000/icon.png")));
assert!(config.allows_host(&parsed("http://localhost:8000/")));
}
#[test]
fn allows_host_requires_the_same_port() {
let config = NetConfig::default().with_overrides(None, None, None, Some("http://localhost:8000"));
assert!(
!config.allows_host(&parsed("http://localhost:9090/icon.png")),
"a different port must not be treated as the configured endpoint"
);
}
#[test]
fn allows_host_ignores_hosts_that_were_not_configured() {
let config = NetConfig::default().with_overrides(None, None, Some("http://localhost:8000"), None);
assert!(config.allows_host(&parsed("http://localhost:8000/icon.png")));
assert!(!config.allows_host(&parsed("http://127.0.0.1:8000/icon.png")));
assert!(!config.allows_host(&parsed("http://example.com/icon.png")));
}
#[test]
fn allows_host_is_false_when_no_custom_endpoint_is_set() {
let config = NetConfig::default();
assert!(!config.allows_host(&parsed("http://localhost:8000/icon.png")));
}
}