Refactor Tauri crate into a library and add OS version header

This commit is contained in:
2026-07-08 21:29:26 +03:00
parent 2e80f7b8eb
commit a2581187da
17 changed files with 244 additions and 244 deletions

1
src-tauri/Cargo.lock generated
View File

@@ -2326,6 +2326,7 @@ dependencies = [
"tauri-plugin-dialog",
"url",
"uuid",
"winreg",
]
[[package]]

View File

@@ -22,3 +22,6 @@ reqwest = { version = "0.12", default-features = false, features = ["blocking",
percent-encoding = "2"
url = "2"
uuid = { version = "1", features = ["v4"] }
[target.'cfg(windows)'.dependencies]
winreg = "0.55"

View File

@@ -1,4 +1,3 @@
#[cfg(not(test))]
use crate::adapters::proxy_router::{
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
ProxyRouterRequest,
@@ -7,11 +6,6 @@ use crate::models::{
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol,
ProxyProtocol, Target,
};
#[cfg(test)]
use crate::proxy_router::{
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
ProxyRouterRequest,
};
use serde::{Deserialize, Serialize};
pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre";

View File

@@ -1,11 +1,8 @@
#[cfg(not(test))]
use crate::adapters::proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, ProxiFyreProxy};
#[cfg(not(test))]
use crate::adapters::proxy_router::{
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
ProxyRouterRequest,
};
#[cfg(not(test))]
use crate::adapters::singbox::{
SingBoxAdapter, SingBoxCheckResult, SingBoxCommandChecker, SingBoxConfigChecker,
SingBoxConfigError, SingBoxConfigErrorKind, SingBoxGeneratedConfig, SingBoxGenerationRequest,
@@ -21,18 +18,6 @@ use crate::models::{
SubscriptionCache, SubscriptionServer, Target, TargetInput, TargetKind,
};
use crate::process::command_no_window;
#[cfg(test)]
use crate::proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, ProxiFyreProxy};
#[cfg(test)]
use crate::proxy_router::{
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
ProxyRouterRequest,
};
#[cfg(test)]
use crate::singbox::{
SingBoxAdapter, SingBoxCheckResult, SingBoxCommandChecker, SingBoxConfigChecker,
SingBoxConfigError, SingBoxConfigErrorKind, SingBoxGeneratedConfig, SingBoxGenerationRequest,
};
use crate::singbox_service::{
build_singbox_setup_status, ensure_safe_singbox_install_dir,
parse_service_command_output as parse_singbox_service_command_output, service_control_script,
@@ -1327,10 +1312,29 @@ pub fn apply_profiles_with_services(
adapter: &impl ProxyRouterAdapter,
helper: &impl ProxyApplyHelper,
clock: &impl Clock,
) -> Result<ApplyProfilesResponse, CommandError> {
apply_profiles_with_services_and_detection(
storage,
adapter,
helper,
clock,
detect_proxyfier_install(),
detect_singbox_install(),
)
}
pub fn apply_profiles_with_services_and_detection(
storage: &JsonStorage,
adapter: &impl ProxyRouterAdapter,
helper: &impl ProxyApplyHelper,
clock: &impl Clock,
detected_proxyfier: Option<DetectedProxyfier>,
detected_singbox: Option<DetectedSingBox>,
) -> Result<ApplyProfilesResponse, CommandError> {
let profiles = storage.read_profiles().map_err(storage_error)?;
let targets = storage.read_targets().map_err(storage_error)?;
let components = components_or_defaults(storage)?;
let components =
components_or_defaults_with_detection(storage, detected_proxyfier, detected_singbox)?;
let generated =
match adapter.generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) {
Ok(generated) => generated,
@@ -2441,7 +2445,7 @@ try {{
}
}
pub(crate) fn singbox_installer_runner_script(
pub fn singbox_installer_runner_script(
installer_path: &Path,
result_path: &Path,
installer_args: &[String],
@@ -2560,11 +2564,23 @@ fn elevated_singbox_service_failed_message(
}
fn components_or_defaults(storage: &JsonStorage) -> Result<Vec<ComponentStatus>, CommandError> {
components_or_defaults_with_detection(
storage,
detect_proxyfier_install(),
detect_singbox_install(),
)
}
fn components_or_defaults_with_detection(
storage: &JsonStorage,
detected_proxyfier: Option<DetectedProxyfier>,
detected_singbox: Option<DetectedSingBox>,
) -> Result<Vec<ComponentStatus>, CommandError> {
let components = storage.read_components().map_err(storage_error)?;
Ok(resolve_component_statuses(
components,
detect_proxyfier_install(),
detect_singbox_install(),
detected_proxyfier,
detected_singbox,
))
}
@@ -3443,7 +3459,7 @@ try {{
}
}
pub(crate) fn wrap_elevated_package_script(body: &str, result_path: &Path) -> String {
pub fn wrap_elevated_package_script(body: &str, result_path: &Path) -> String {
let mut script = String::new();
script.push_str("$ErrorActionPreference = 'Stop'\n");
script.push_str(&format!(
@@ -3530,7 +3546,7 @@ fn powershell_output_message(output: &Output, fallback: &str) -> String {
fallback.to_string()
}
pub(crate) fn install_proxifyre_script(generated_config_path: &Path) -> String {
pub fn install_proxifyre_script(generated_config_path: &Path) -> String {
let mut script = String::new();
script.push_str(&format!(
"$targetDir = '{}'\n",

View File

@@ -1,5 +1,59 @@
pub mod activity;
pub mod commands;
pub mod component_detection;
pub mod helper;
pub mod models;
pub mod process;
pub mod singbox_service;
pub mod storage;
pub mod subscription;
pub mod validation;
pub mod adapters {
pub mod proxifyre;
pub mod proxy_router;
pub mod singbox;
}
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.manage(commands::CommandState::default())
.invoke_handler(tauri::generate_handler![
commands::get_status,
commands::get_admin_status,
commands::restart_as_admin,
commands::get_startup_snapshot,
commands::get_profiles,
commands::get_saved_state,
commands::save_profile,
commands::get_targets,
commands::save_target,
commands::get_components,
commands::get_proxifyre_setup_status,
commands::get_singbox_status,
commands::get_singbox_setup_status,
commands::resolve_profile_preview,
commands::save_singbox_subscription,
commands::fetch_singbox_subscription,
commands::forget_singbox_subscription,
commands::select_singbox_server,
commands::ping_singbox_server,
commands::ping_all_singbox_servers,
commands::ping_proxy_target,
commands::generate_singbox_config,
commands::apply_profiles,
commands::get_logs,
commands::open_config_location,
commands::start_proxifyre_service,
commands::stop_proxifyre_service,
commands::install_proxifyre,
commands::uninstall_proxifyre,
commands::start_singbox_service,
commands::stop_singbox_service,
commands::install_singbox,
commands::uninstall_singbox
])
.run(tauri::generate_context!())
.expect("не удалось запустить клиент ProxyWarden");
}

View File

@@ -1,75 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod activity;
mod commands;
mod component_detection;
mod models;
mod process;
mod singbox_service;
mod storage;
mod subscription;
mod validation;
mod adapters {
pub mod proxifyre;
pub mod proxy_router;
pub mod singbox;
}
#[cfg(test)]
pub(crate) mod proxifyre {
pub use crate::adapters::proxifyre::*;
}
#[cfg(test)]
pub(crate) mod proxy_router {
pub use crate::adapters::proxy_router::*;
}
#[cfg(test)]
pub(crate) mod singbox {
pub use crate::adapters::singbox::*;
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.manage(commands::CommandState::default())
.invoke_handler(tauri::generate_handler![
commands::get_status,
commands::get_admin_status,
commands::restart_as_admin,
commands::get_startup_snapshot,
commands::get_profiles,
commands::get_saved_state,
commands::save_profile,
commands::get_targets,
commands::save_target,
commands::get_components,
commands::get_proxifyre_setup_status,
commands::get_singbox_status,
commands::get_singbox_setup_status,
commands::resolve_profile_preview,
commands::save_singbox_subscription,
commands::fetch_singbox_subscription,
commands::forget_singbox_subscription,
commands::select_singbox_server,
commands::ping_singbox_server,
commands::ping_all_singbox_servers,
commands::ping_proxy_target,
commands::generate_singbox_config,
commands::apply_profiles,
commands::get_logs,
commands::open_config_location,
commands::start_proxifyre_service,
commands::stop_proxifyre_service,
commands::install_proxifyre,
commands::uninstall_proxifyre,
commands::start_singbox_service,
commands::stop_singbox_service,
commands::install_singbox,
commands::uninstall_singbox
])
.run(tauri::generate_context!())
.expect("не удалось запустить клиент ProxyWarden");
proxywarden_lib::run();
}

View File

@@ -40,6 +40,7 @@ pub struct SubscriptionFetchIdentity {
pub app_name: String,
pub user_agent: String,
pub device_os: String,
pub device_os_version: Option<String>,
pub device_model: String,
}
@@ -64,6 +65,7 @@ impl Default for SubscriptionFetchIdentity {
app_name: DEFAULT_APP_NAME.to_string(),
user_agent: format!("{DEFAULT_APP_NAME}/{device_os}"),
device_os,
device_os_version: detect_device_os_version(),
device_model: DEFAULT_APP_NAME.to_string(),
}
}
@@ -124,6 +126,18 @@ pub fn fetch_subscription_with_identity(
.header("x-device-os", identity.device_os.as_str())
.header("x-device-model", identity.device_model.as_str());
if let Some(device_os_version) = identity
.device_os_version
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
let header_value = sanitize_header_value(device_os_version);
if !header_value.is_empty() {
request = request.header("x-device-os-version", header_value);
}
}
if let Some(device_hwid) = identity
.device_hwid
.as_deref()
@@ -318,6 +332,86 @@ fn query_value(url: &Url, key: &str) -> Option<String> {
.map(|(_, value)| value.into_owned())
}
fn sanitize_header_value(value: &str) -> String {
value
.chars()
.filter(|ch| ch.is_ascii_graphic() || *ch == ' ')
.collect::<String>()
}
fn detect_device_os_version() -> Option<String> {
#[cfg(windows)]
{
windows_device_os_version()
}
#[cfg(not(windows))]
{
None
}
}
#[cfg(windows)]
fn windows_device_os_version() -> Option<String> {
use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey};
let current_version = RegKey::predef(HKEY_LOCAL_MACHINE)
.open_subkey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")
.ok()?;
let product_name = current_version
.get_value::<String, _>("ProductName")
.ok()
.map(|value| normalize_windows_product_name(&value, &current_version))
.filter(|value| !value.trim().is_empty());
let display_version = current_version
.get_value::<String, _>("DisplayVersion")
.ok()
.or_else(|| current_version.get_value::<String, _>("ReleaseId").ok())
.filter(|value| !value.trim().is_empty());
let build = current_version
.get_value::<String, _>("CurrentBuildNumber")
.ok()
.or_else(|| current_version.get_value::<String, _>("CurrentBuild").ok())
.filter(|value| !value.trim().is_empty());
let ubr = current_version.get_value::<u32, _>("UBR").ok();
let build = match (build, ubr) {
(Some(build), Some(ubr)) => Some(format!("{build}.{ubr}")),
(build, _) => build,
};
let mut parts = Vec::new();
if let Some(product_name) = product_name {
parts.push(product_name);
}
if let Some(display_version) = display_version {
parts.push(display_version);
}
if let Some(build) = build {
parts.push(format!("build {build}"));
}
let version = parts.join(" | ");
(!version.is_empty()).then_some(version)
}
#[cfg(windows)]
fn normalize_windows_product_name(value: &str, current_version: &winreg::RegKey) -> String {
let trimmed = value.trim();
let build_number = current_version
.get_value::<String, _>("CurrentBuildNumber")
.ok()
.or_else(|| current_version.get_value::<String, _>("CurrentBuild").ok())
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or_default();
if build_number >= 22000 && trimmed.starts_with("Windows 10") {
return trimmed.replacen("Windows 10", "Windows 11", 1);
}
trimmed.to_string()
}
fn now_timestamp() -> String {
let seconds = SystemTime::now()
.duration_since(UNIX_EPOCH)

View File

@@ -1,42 +1,19 @@
#[path = "../src/activity.rs"]
mod activity;
#[path = "../src/commands.rs"]
mod commands;
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/process.rs"]
mod process;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/singbox.rs"]
mod singbox;
#[path = "../src/singbox_service.rs"]
mod singbox_service;
#[path = "../src/storage.rs"]
mod storage;
#[path = "../src/subscription.rs"]
mod subscription;
#[path = "../src/validation.rs"]
mod validation;
use commands::{
apply_profiles_with_services, build_status, read_saved_state_with_proxifyre_config,
resolve_component_statuses, resolve_preview, save_profile_to_storage, save_target_to_storage,
Clock, CommandError, DetectedProxyApplyHelper, HelperApplyRequest, HelperApplyResult,
ProfileInputDto, ProfileItemInputDto, ProxyApplyHelper, TargetInputDto,
use proxywarden_lib::adapters::proxifyre::ProxiFyreAdapter;
use proxywarden_lib::commands::{
self, apply_profiles_with_services, apply_profiles_with_services_and_detection, build_status,
read_saved_state_with_proxifyre_config, resolve_component_statuses, resolve_preview,
save_profile_to_storage, save_target_to_storage, Clock, CommandError, DetectedProxyApplyHelper,
HelperApplyRequest, HelperApplyResult, ProfileInputDto, ProfileItemInputDto, ProxyApplyHelper,
TargetInputDto,
};
use component_detection::{
use proxywarden_lib::component_detection::{
DetectedProxyfier, ProxyfierDetectionHost, ProxyfierEngine, RegistryInstallEntry,
};
use models::{
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol,
ProxyProtocol, Target, TargetKind,
use proxywarden_lib::models::{
self, ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType,
Protocol, ProxyProtocol, Target, TargetKind,
};
use proxifyre::ProxiFyreAdapter;
use proxywarden_lib::storage::JsonStorage;
use std::collections::HashSet;
use std::fs;
use std::net::TcpListener;
@@ -44,7 +21,6 @@ use std::path::{Path, PathBuf};
#[cfg(windows)]
use std::process::Command as ProcessCommand;
use std::time::{SystemTime, UNIX_EPOCH};
use storage::JsonStorage;
#[test]
fn save_commands_normalize_and_persist_profile_and_target() {
@@ -354,11 +330,13 @@ fn apply_blocks_local_singbox_target_when_component_is_missing() {
.expect("write targets");
write_json(&storage.paths().components_file, &[singbox_missing()]);
let error = apply_profiles_with_services(
let error = apply_profiles_with_services_and_detection(
&storage,
&ProxiFyreAdapter::default(),
&MockApplyHelper,
&FixedClock,
None,
None,
)
.expect_err("missing sing-box should block local target apply");
let activity = storage.read_activity().expect("read blocked activity");

View File

@@ -1,16 +1,9 @@
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/process.rs"]
mod process;
use component_detection::{
use proxywarden_lib::component_detection::{
detect_proxyfier_install_with_host, detect_singbox_install_with_host,
proxyfier_component_from_detection, singbox_component_from_detection, ProxyfierDetectionHost,
ProxyfierEngine, RegistryInstallEntry,
};
use models::ComponentState;
use proxywarden_lib::models::ComponentState;
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},

View File

@@ -1,13 +1,8 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/validation.rs"]
mod validation;
use models::{
use proxywarden_lib::models::{
ComponentId, ProfileInput, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol,
TargetInput, TargetKind,
};
use validation::{normalize_profile, normalize_target};
use proxywarden_lib::validation::{normalize_profile, normalize_target};
#[test]
fn normalizes_profile_source_items() {

View File

@@ -1,14 +1,9 @@
#[path = "../src/helper.rs"]
mod helper;
#[path = "../src/models.rs"]
mod models;
use helper::{
use proxywarden_lib::helper::{
helper_action_requires_elevation, install_request, parse_helper_response,
proxifyre_apply_request, service_request, HelperAction, HelperCommandOutput,
HelperCommandRunner, HelperCommandSpec, HelperError, HelperResponse, StructuredHelper,
};
use models::ComponentId;
use proxywarden_lib::models::ComponentId;
use serde_json::json;
use std::cell::RefCell;
use std::path::PathBuf;

View File

@@ -1,16 +1,13 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
use models::{
use proxywarden_lib::adapters::proxifyre::{
ProxiFyreAdapter, ProxiFyreConfig, PROXIFYRE_OUTPUT_FILE,
};
use proxywarden_lib::adapters::proxy_router::{
ProxyRouterAdapter, ProxyRouterErrorKind, ProxyRouterRequest,
};
use proxywarden_lib::models::{
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol,
ProxyProtocol, Target, TargetKind,
};
use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, PROXIFYRE_OUTPUT_FILE};
use proxy_router::{ProxyRouterAdapter, ProxyRouterErrorKind, ProxyRouterRequest};
#[test]
fn generates_proxifyre_config_for_discord_external_socks5_target() {

View File

@@ -1,26 +1,15 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/process.rs"]
mod process;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/singbox.rs"]
mod singbox;
use models::{
ComponentId, ComponentState, ComponentStatus, LocalSingBoxConfig, Profile, ProfileItem,
ProfileItemType, Protocol, ProxyProtocol, SubscriptionCache, SubscriptionServer, Target,
TargetKind,
};
use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig};
use proxy_router::{ProxyRouterAdapter, ProxyRouterRequest};
use singbox::{
use proxywarden_lib::adapters::proxifyre::{ProxiFyreAdapter, ProxiFyreConfig};
use proxywarden_lib::adapters::proxy_router::{ProxyRouterAdapter, ProxyRouterRequest};
use proxywarden_lib::adapters::singbox::{
SingBoxAdapter, SingBoxCheckResult, SingBoxConfigChecker, SingBoxConfigError,
SingBoxConfigErrorKind, SingBoxGenerationRequest, DEFAULT_VPN_OUTBOUND_TAG,
SINGBOX_OUTPUT_FILE,
};
use proxywarden_lib::models::{
ComponentId, ComponentState, ComponentStatus, LocalSingBoxConfig, Profile, ProfileItem,
ProfileItemType, Protocol, ProxyProtocol, SubscriptionCache, SubscriptionServer, Target,
TargetKind,
};
use std::{
cell::RefCell,
path::{Path, PathBuf},

View File

@@ -1,44 +1,22 @@
#[path = "../src/activity.rs"]
mod activity;
#[path = "../src/commands.rs"]
mod commands;
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/process.rs"]
mod process;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/singbox.rs"]
mod singbox;
#[path = "../src/singbox_service.rs"]
mod singbox_service;
#[path = "../src/storage.rs"]
mod storage;
#[path = "../src/subscription.rs"]
mod subscription;
#[path = "../src/validation.rs"]
mod validation;
use commands::{
use proxywarden_lib::adapters::singbox::{
SingBoxAdapter, SingBoxCheckResult, SingBoxConfigChecker, SingBoxConfigError,
};
use proxywarden_lib::commands::{
fetch_singbox_subscription_with_fetcher, forget_singbox_subscription_in_storage,
generate_singbox_config_with_services, save_singbox_subscription_to_storage,
select_singbox_server_in_storage, Clock, SaveSingBoxSubscriptionInputDto,
SelectSingBoxServerInputDto, SubscriptionFetcher,
};
use models::{
use proxywarden_lib::models::{
ActivityLevel, ComponentId, LocalSingBoxConfig, ProxyProtocol, SubscriptionCache,
SubscriptionServer, TargetKind,
};
use proxywarden_lib::storage::JsonStorage;
use proxywarden_lib::subscription;
use serde_json::{json, Map};
use singbox::{SingBoxAdapter, SingBoxCheckResult, SingBoxConfigChecker, SingBoxConfigError};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use storage::JsonStorage;
#[test]
fn saves_subscription_url_without_exposing_secret_query() {

View File

@@ -1,14 +1,5 @@
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/process.rs"]
mod process;
#[path = "../src/singbox_service.rs"]
mod singbox_service;
use component_detection::DetectedSingBox;
use singbox_service::{
use proxywarden_lib::component_detection::DetectedSingBox;
use proxywarden_lib::singbox_service::{
build_singbox_setup_status, ensure_safe_singbox_install_dir, parse_service_command_output,
service_control_script, SingBoxServiceAction,
};

View File

@@ -1,19 +1,12 @@
#[path = "../src/activity.rs"]
mod activity;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/storage.rs"]
mod storage;
use models::{
use proxywarden_lib::models::{
ActivityEntry, ActivityLevel, ComponentId, ComponentState, ComponentStatus, LocalSingBoxConfig,
Profile, ProfileItem, ProfileItemType, Protocol, ProxyProtocol, SubscriptionCache,
SubscriptionServer, Target, TargetKind,
};
use proxywarden_lib::storage::{backup_path, default_config_root, JsonStorage, StoragePaths};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use storage::{backup_path, default_config_root, JsonStorage, StoragePaths};
#[test]
fn storage_defaults_to_programdata_root() {

View File

@@ -1,14 +1,11 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/subscription.rs"]
mod subscription;
use base64::{engine::general_purpose, Engine};
use models::redact_subscription_url;
use proxywarden_lib::models::redact_subscription_url;
use proxywarden_lib::subscription::{
self, parse_subscription_body, parse_user_info, SubscriptionFetchIdentity,
};
use std::io::{Read, Write};
use std::net::TcpListener;
use std::time::Duration;
use subscription::{parse_subscription_body, parse_user_info, SubscriptionFetchIdentity};
#[test]
fn parses_singbox_json_config_servers() {
@@ -130,7 +127,8 @@ fn fetch_subscription_sends_device_hwid_header_when_identity_is_set() {
String::from_utf8_lossy(&request).to_ascii_lowercase()
});
let identity = SubscriptionFetchIdentity::with_device_hwid(Some("hwid-abc123"));
let mut identity = SubscriptionFetchIdentity::with_device_hwid(Some("hwid-abc123"));
identity.device_os_version = Some("Windows 11 Pro | 25H2 | build 26200.8655".to_string());
let cache = subscription::fetch_subscription_with_identity(&url, &identity)
.expect("fetch subscription through local test server");
let request = request_thread.join().expect("request thread");
@@ -140,6 +138,7 @@ fn fetch_subscription_sends_device_hwid_header_when_identity_is_set() {
assert!(request.contains("user-agent: proxywarden/"));
assert!(request.contains("x-app-name: proxywarden"));
assert!(request.contains("x-device-os:"));
assert!(request.contains("x-device-os-version: windows 11 pro | 25h2 | build 26200.8655"));
assert!(request.contains("x-device-model: proxywarden"));
}