Clarify active Windows client architecture
This commit is contained in:
242
apps/windows-client/src-tauri/src/adapters/proxifyre.rs
Normal file
242
apps/windows-client/src-tauri/src/adapters/proxifyre.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use crate::models::{
|
||||
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol,
|
||||
ProxyProtocol, Target,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use crate::proxy_router::{
|
||||
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
||||
ProxyRouterRequest,
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
use crate::adapters::proxy_router::{
|
||||
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
||||
ProxyRouterRequest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre";
|
||||
pub const PROXIFYRE_OUTPUT_FILE: &str = "proxifyre-app-config.json";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProxiFyreAdapter {
|
||||
log_level: String,
|
||||
bypass_lan: bool,
|
||||
}
|
||||
|
||||
impl ProxiFyreAdapter {
|
||||
pub fn new(log_level: impl Into<String>, bypass_lan: bool) -> Self {
|
||||
Self {
|
||||
log_level: log_level.into(),
|
||||
bypass_lan,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_proxifyre_config(
|
||||
&self,
|
||||
request: ProxyRouterRequest<'_>,
|
||||
) -> Result<ProxiFyreConfig, ProxyRouterError> {
|
||||
let mut proxies = Vec::new();
|
||||
|
||||
for profile in request.profiles.iter().filter(|profile| profile.enabled) {
|
||||
let target = find_target(profile, request.targets)?;
|
||||
ensure_target_supported(profile, target, request.components)?;
|
||||
|
||||
let app_names = app_names_for_profile(profile);
|
||||
if app_names.is_empty() {
|
||||
return Err(ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::EmptyProfileItems,
|
||||
format!("В профиле '{}' нет приложений для маршрутизации", profile.id),
|
||||
));
|
||||
}
|
||||
|
||||
proxies.push(ProxiFyreProxy {
|
||||
app_names,
|
||||
socks5_proxy_endpoint: format!("{}:{}", target.host, target.port),
|
||||
supported_protocols: protocols_for_profile(profile),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ProxiFyreConfig {
|
||||
log_level: self.log_level.clone(),
|
||||
bypass_lan: self.bypass_lan,
|
||||
proxies,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProxiFyreAdapter {
|
||||
fn default() -> Self {
|
||||
Self::new("Info", true)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProxyRouterAdapter for ProxiFyreAdapter {
|
||||
fn id(&self) -> &'static str {
|
||||
PROXIFYRE_ADAPTER_ID
|
||||
}
|
||||
|
||||
fn output_file_name(&self) -> &'static str {
|
||||
PROXIFYRE_OUTPUT_FILE
|
||||
}
|
||||
|
||||
fn generate_config(
|
||||
&self,
|
||||
request: ProxyRouterRequest<'_>,
|
||||
) -> Result<ProxyRouterGeneratedConfig, ProxyRouterError> {
|
||||
let config = self.generate_proxifyre_config(request)?;
|
||||
let enabled_profiles = config.proxies.len();
|
||||
let routed_apps = config
|
||||
.proxies
|
||||
.iter()
|
||||
.map(|proxy| proxy.app_names.len())
|
||||
.sum();
|
||||
let contents = serde_json::to_string_pretty(&config).map_err(|error| {
|
||||
ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::Serialization,
|
||||
format!("Не удалось сериализовать конфиг ProxiFyre: {error}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(ProxyRouterGeneratedConfig {
|
||||
adapter_id: self.id().to_string(),
|
||||
output_file_name: self.output_file_name().to_string(),
|
||||
contents,
|
||||
enabled_profiles,
|
||||
routed_apps,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProxiFyreConfig {
|
||||
#[serde(rename = "logLevel")]
|
||||
pub log_level: String,
|
||||
#[serde(rename = "bypassLan")]
|
||||
pub bypass_lan: bool,
|
||||
pub proxies: Vec<ProxiFyreProxy>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProxiFyreProxy {
|
||||
#[serde(rename = "appNames")]
|
||||
pub app_names: Vec<String>,
|
||||
#[serde(rename = "socks5ProxyEndpoint")]
|
||||
pub socks5_proxy_endpoint: String,
|
||||
#[serde(rename = "supportedProtocols")]
|
||||
pub supported_protocols: Vec<String>,
|
||||
}
|
||||
|
||||
fn find_target<'a>(
|
||||
profile: &Profile,
|
||||
targets: &'a [Target],
|
||||
) -> Result<&'a Target, ProxyRouterError> {
|
||||
targets
|
||||
.iter()
|
||||
.find(|target| target.id == profile.target_id)
|
||||
.ok_or_else(|| {
|
||||
ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::MissingTarget,
|
||||
format!(
|
||||
"Профиль '{}' ссылается на отсутствующую цель '{}'",
|
||||
profile.id, profile.target_id
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_target_supported(
|
||||
profile: &Profile,
|
||||
target: &Target,
|
||||
components: &[ComponentStatus],
|
||||
) -> Result<(), ProxyRouterError> {
|
||||
if target.protocol != ProxyProtocol::Socks5 {
|
||||
return Err(ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::UnsupportedTargetProtocol,
|
||||
format!(
|
||||
"Цель '{}' использует HTTP, но ProxiFyre требует SOCKS5",
|
||||
target.id
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(required_component) = &target.requires_component {
|
||||
let Some(status) = components
|
||||
.iter()
|
||||
.find(|component| &component.id == required_component)
|
||||
else {
|
||||
return Err(ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::MissingRequiredComponent,
|
||||
format!(
|
||||
"Цель '{}' профиля '{}' требует отсутствующий компонент '{}'",
|
||||
target.id,
|
||||
profile.id,
|
||||
component_id_label(required_component)
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
if !component_is_running(status) {
|
||||
return Err(ProxyRouterError::new(
|
||||
ProxyRouterErrorKind::RequiredComponentNotRunning,
|
||||
format!(
|
||||
"Цель '{}' профиля '{}' требует запущенный компонент '{}'",
|
||||
target.id,
|
||||
profile.id,
|
||||
component_id_label(required_component)
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn component_is_running(status: &ComponentStatus) -> bool {
|
||||
status.installed && status.running && status.state == ComponentState::Running
|
||||
}
|
||||
|
||||
fn app_names_for_profile(profile: &Profile) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
|
||||
for item in &profile.items {
|
||||
let value = item.value.trim();
|
||||
if value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let app_name = match item.item_type {
|
||||
ProfileItemType::Process | ProfileItemType::Folder | ProfileItemType::Exe => value,
|
||||
};
|
||||
|
||||
if !names.iter().any(|existing| existing == app_name) {
|
||||
names.push(app_name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
fn protocols_for_profile(profile: &Profile) -> Vec<String> {
|
||||
let mut protocols = Vec::new();
|
||||
|
||||
for protocol in &profile.protocols {
|
||||
let value = match protocol {
|
||||
Protocol::Tcp => "TCP",
|
||||
Protocol::Udp => "UDP",
|
||||
};
|
||||
|
||||
if !protocols.iter().any(|existing| existing == value) {
|
||||
protocols.push(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
protocols
|
||||
}
|
||||
|
||||
fn component_id_label(component_id: &ComponentId) -> &'static str {
|
||||
match component_id {
|
||||
ComponentId::ControlApp => "control-app",
|
||||
ComponentId::Proxyfier => "proxyfier",
|
||||
ComponentId::Singbox => "singbox",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user