mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
chore: Coco app http request headers (#744)
Add the following HTTP headers when making HTTP requests: - X-OS-NAME - X-OS-VER - X-OS-ARCH - X-APP-NAME - X-APP-VER - X-APP-LANG
This commit is contained in:
@@ -38,6 +38,7 @@ Information about release notes of Coco Server is provided here.
|
||||
- chore: assistant params & styles #753
|
||||
- chore: make optional fields optional #758
|
||||
- chore: search-chat components add formatUrl & think data & icons url #765
|
||||
- chore: Coco app http request headers #744
|
||||
|
||||
## 0.6.0 (2025-06-29)
|
||||
|
||||
@@ -326,4 +327,4 @@ Information about release notes of Coco Server is provided here.
|
||||
|
||||
### Bug fix
|
||||
|
||||
### Improvements
|
||||
### Improvements
|
||||
34
src-tauri/Cargo.lock
generated
34
src-tauri/Cargo.lock
generated
@@ -876,6 +876,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_plain",
|
||||
"strsim 0.10.0",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-nspanel",
|
||||
@@ -3666,6 +3667,15 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigfloat"
|
||||
version = "1.7.2"
|
||||
@@ -3984,6 +3994,16 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.1"
|
||||
@@ -5857,6 +5877,20 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
|
||||
@@ -105,6 +105,7 @@ url = "2.5.2"
|
||||
camino = "1.1.10"
|
||||
tokio-stream = { version = "0.1.17", features = ["io-util"] }
|
||||
cfg-if = "1.0.1"
|
||||
sysinfo = "0.35.2"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
|
||||
@@ -129,4 +130,4 @@ tauri-plugin-updater = { git = "https://github.com/infinilabs/plugins-workspace"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
enigo="0.3"
|
||||
windows = { version = "0.61.3", features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Ole", "Win32_System_Search", "Win32_UI_Shell_PropertiesSystem", "Win32_Data"] }
|
||||
windows = { version = "0.61.3", features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Ole", "Win32_System_Search", "Win32_UI_Shell_PropertiesSystem", "Win32_Data"] }
|
||||
|
||||
@@ -13,6 +13,7 @@ use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use tauri::Manager;
|
||||
use third_party::THIRD_PARTY_EXTENSIONS_SEARCH_SOURCE;
|
||||
use crate::util::platform::Platform;
|
||||
|
||||
pub const LOCAL_QUERY_SOURCE_TYPE: &str = "local";
|
||||
const PLUGIN_JSON_FILE_NAME: &str = "plugin.json";
|
||||
@@ -22,17 +23,6 @@ fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Copy, Clone, Hash, PartialEq, Eq, Display)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
enum Platform {
|
||||
#[display("macOS")]
|
||||
Macos,
|
||||
#[display("Linux")]
|
||||
Linux,
|
||||
#[display("windows")]
|
||||
Windows,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Extension {
|
||||
/// Extension ID.
|
||||
|
||||
11
src-tauri/src/extension/third_party/mod.rs
vendored
11
src-tauri/src/extension/third_party/mod.rs
vendored
@@ -4,7 +4,7 @@ use super::alter_extension_json_file;
|
||||
use super::canonicalize_relative_icon_path;
|
||||
use super::Extension;
|
||||
use super::ExtensionType;
|
||||
use super::Platform;
|
||||
use crate::util::platform::Platform;
|
||||
use super::LOCAL_QUERY_SOURCE_TYPE;
|
||||
use super::PLUGIN_JSON_FILE_NAME;
|
||||
use crate::common::document::open;
|
||||
@@ -48,13 +48,6 @@ pub(crate) static THIRD_PARTY_EXTENSIONS_DIRECTORY: LazyLock<PathBuf> = LazyLock
|
||||
app_data_dir
|
||||
});
|
||||
|
||||
/// Helper function to determine the current platform.
|
||||
fn current_platform() -> Platform {
|
||||
let os_str = std::env::consts::OS;
|
||||
serde_plain::from_str(os_str).unwrap_or_else(|_e| {
|
||||
panic!("std::env::consts::OS is [{}], which is not a valid value for [enum Platform], valid values: ['macos', 'linux', 'windows']", os_str)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn list_third_party_extensions(
|
||||
directory: &Path,
|
||||
@@ -62,7 +55,7 @@ pub(crate) async fn list_third_party_extensions(
|
||||
let mut found_invalid_extensions = false;
|
||||
|
||||
let mut extensions_dir_iter = read_dir(&directory).await.map_err(|e| e.to_string())?;
|
||||
let current_platform = current_platform();
|
||||
let current_platform = Platform::current();
|
||||
|
||||
let mut extensions = Vec::new();
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ pub fn run() {
|
||||
extension::built_in::file_search::config::set_file_system_config,
|
||||
server::synthesize::synthesize,
|
||||
util::file::get_file_icon,
|
||||
util::app_lang::update_app_lang,
|
||||
])
|
||||
.setup(|app| {
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use crate::server::servers::{get_server_by_id, get_server_token};
|
||||
use crate::util::app_lang::get_app_lang;
|
||||
use crate::util::platform::Platform;
|
||||
use http::{HeaderName, HeaderValue};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{Client, Method, RequestBuilder};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -26,6 +29,26 @@ pub static HTTP_CLIENT: Lazy<Mutex<Client>> = Lazy::new(|| {
|
||||
Mutex::new(new_reqwest_http_client(allow_self_signature))
|
||||
});
|
||||
|
||||
/// These header values won't change during a process's lifetime.
|
||||
static STATIC_HEADERS: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
(
|
||||
"X-OS-NAME".into(),
|
||||
Platform::current()
|
||||
.to_os_name_http_header_str()
|
||||
.into_owned(),
|
||||
),
|
||||
(
|
||||
"X-OS-VER".into(),
|
||||
sysinfo::System::os_version()
|
||||
.expect("sysinfo::System::os_version() should be Some on major systems"),
|
||||
),
|
||||
("X-OS-ARCH".into(), sysinfo::System::cpu_arch()),
|
||||
("X-APP-NAME".into(), "coco-app".into()),
|
||||
("X-APP-VER".into(), env!("CARGO_PKG_VERSION").into()),
|
||||
])
|
||||
});
|
||||
|
||||
pub struct HttpClient;
|
||||
|
||||
impl HttpClient {
|
||||
@@ -81,8 +104,32 @@ impl HttpClient {
|
||||
// Build the request
|
||||
let mut request_builder = client.request(method.clone(), url);
|
||||
|
||||
// Populate the headers defined by us
|
||||
let mut req_headers = reqwest::header::HeaderMap::new();
|
||||
for (key, value) in STATIC_HEADERS.iter() {
|
||||
let key = HeaderName::from_bytes(key.as_bytes())
|
||||
.expect("headers defined by us should be valid");
|
||||
let value = HeaderValue::from_str(value.trim()).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"header value [{}] is invalid, error [{}], this should be unreachable",
|
||||
value, e
|
||||
);
|
||||
});
|
||||
req_headers.insert(key, value);
|
||||
}
|
||||
let app_lang = get_app_lang().await.to_string();
|
||||
req_headers.insert(
|
||||
"X-APP-LANG",
|
||||
HeaderValue::from_str(&app_lang).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"header value [{}] is invalid, error [{}], this should be unreachable",
|
||||
app_lang, e
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
// Headers from the function parameter
|
||||
if let Some(h) = headers {
|
||||
let mut req_headers = reqwest::header::HeaderMap::new();
|
||||
for (key, value) in h.into_iter() {
|
||||
match (
|
||||
HeaderName::from_bytes(key.as_bytes()),
|
||||
|
||||
62
src-tauri/src/util/app_lang.rs
Normal file
62
src-tauri/src/util/app_lang.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
//! Configuration entry App language is persisted in the frontend code, but we
|
||||
//! need to access it on the backend.
|
||||
//!
|
||||
//! So we duplicate it here **in the MEMORY** and expose a setter method to the
|
||||
//! frontend so that the value can be updated and stay update-to-date.
|
||||
|
||||
use function_name::named;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) enum Lang {
|
||||
en_US,
|
||||
zh_CN,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Lang {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Lang::en_US => write!(f, "en_US"),
|
||||
Lang::zh_CN => write!(f, "zh_CN"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Lang {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"en" => Ok(Lang::en_US),
|
||||
"zh" => Ok(Lang::zh_CN),
|
||||
_ => Err(format!("Invalid language: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache the language config in memory.
|
||||
static APP_LANG: RwLock<Option<Lang>> = RwLock::const_new(None);
|
||||
|
||||
/// Frontend code uses this interface to update the in-memory cached `APP_LANG` config.
|
||||
#[named]
|
||||
#[tauri::command]
|
||||
pub(crate) async fn update_app_lang(lang: String) {
|
||||
let app_lang = lang.parse::<Lang>().unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"frontend code passes an invalid argument [{}] to interface [{}], parsing error [{}]",
|
||||
lang,
|
||||
function_name!(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let mut write_guard = APP_LANG.write().await;
|
||||
*write_guard = Some(app_lang);
|
||||
}
|
||||
|
||||
/// Helper getter method to handle the `None` case.
|
||||
pub(crate) async fn get_app_lang() -> Lang {
|
||||
let opt_lang = *APP_LANG.read().await;
|
||||
opt_lang.expect("frontend code did not invoke [update_app_lang()] to set the APP_LANG")
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod platform;
|
||||
pub(crate) mod app_lang;
|
||||
|
||||
use std::{path::Path, process::Command};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
41
src-tauri/src/util/platform.rs
Normal file
41
src-tauri/src/util/platform.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use derive_more::Display;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Copy, Clone, Hash, PartialEq, Eq, Display)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
pub(crate) enum Platform {
|
||||
#[display("macOS")]
|
||||
Macos,
|
||||
#[display("Linux")]
|
||||
Linux,
|
||||
#[display("windows")]
|
||||
Windows,
|
||||
}
|
||||
|
||||
|
||||
impl Platform {
|
||||
/// Helper function to determine the current platform.
|
||||
pub(crate) fn current() -> Platform {
|
||||
let os_str = std::env::consts::OS;
|
||||
serde_plain::from_str(os_str).unwrap_or_else(|_e| {
|
||||
panic!("std::env::consts::OS is [{}], which is not a valid value for [enum Platform], valid values: ['macos', 'linux', 'windows']", os_str)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the `X-OS-NAME` HTTP request header.
|
||||
pub(crate) fn to_os_name_http_header_str(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
Self::Macos => {
|
||||
Cow::Borrowed("macos")
|
||||
}
|
||||
Self::Windows => {
|
||||
Cow::Borrowed("windows")
|
||||
}
|
||||
// For Linux, we need the actual distro `ID`, not just a "linux".
|
||||
Self::Linux => {
|
||||
Cow::Owned(sysinfo::System::distribution_id())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
change_shortcut,
|
||||
unregister_shortcut,
|
||||
} from "@/commands";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
|
||||
export function ThemeOption({
|
||||
icon: Icon,
|
||||
@@ -74,10 +75,13 @@ export default function GeneralSettings() {
|
||||
|
||||
const [launchAtLogin, setLaunchAtLogin] = useState(true);
|
||||
|
||||
const showTooltip = useAppStore((state) => state.showTooltip);
|
||||
const setShowTooltip = useAppStore((state) => state.setShowTooltip);
|
||||
const language = useAppStore((state) => state.language);
|
||||
const setLanguage = useAppStore((state) => state.setLanguage);
|
||||
const { showTooltip, setShowTooltip, language, setLanguage } = useAppStore();
|
||||
|
||||
useEffect(() => {
|
||||
platformAdapter.invokeBackend("update_app_lang", {
|
||||
lang: language,
|
||||
});
|
||||
}, [language]);
|
||||
|
||||
const fetchAutoStartStatus = async () => {
|
||||
if (isTauri()) {
|
||||
@@ -251,7 +255,9 @@ export default function GeneralSettings() {
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={currentLanguage}
|
||||
onChange={(e) => setLanguage(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setLanguage(e.currentTarget.value);
|
||||
}}
|
||||
className="px-3 py-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="en">{t("settings.language.english")}</option>
|
||||
|
||||
Reference in New Issue
Block a user