Skip to content

Commit acf0e48

Browse files
committed
✨ add feature settings
1 parent 9c6a629 commit acf0e48

17 files changed

Lines changed: 135 additions & 38 deletions

File tree

Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ homepage = "https://github.com/XDSEC/WebSocketReflectorX"
99
license = "MIT"
1010
repository = "https://github.com/XDSEC/WebSocketReflectorX"
1111
rust-version = "1.89.0"
12-
version = "0.5.11"
12+
version = "0.5.12"
1313

1414
[profile.release]
1515
codegen-units = 1
@@ -25,14 +25,14 @@ futures-util = { version = "0.3", features = ["sink"] }
2525
local-ip-address = "0.6"
2626
rustls = { version = "0.23", features = ["ring"] }
2727
thiserror = "2.0"
28-
tokio = { version = "1.47", features = ["full"] }
28+
tokio = { version = "1.48", features = ["full"] }
2929
tokio-util = { version = "0.7", features = ["codec"] }
3030

3131
# optional
32-
tokio-tungstenite = { version = "0.27", features = ["rustls-tls-native-roots"] }
32+
tokio-tungstenite = { version = "0.28", features = ["rustls-tls-native-roots"] }
3333

3434
# binary cli only
35-
bitflags = { version = "2.9" }
35+
bitflags = { version = "2.10" }
3636
chrono = { version = "0.4", features = ["serde"] }
3737
clap = { version = "4.5", features = ["derive"] }
3838
once_cell = { version = "1.21" }
@@ -48,7 +48,7 @@ url = { version = "2.5" }
4848

4949
# GUI
5050
async-compat = { version = "0.2" }
51-
i-slint-backend-winit = "1.12"
51+
i-slint-backend-winit = "1.14"
5252
open = "5.3"
5353

5454
reqwest = { version = "0.12", default-features = false, features = [
@@ -59,7 +59,7 @@ reqwest = { version = "0.12", default-features = false, features = [
5959
"rustls-tls",
6060
] }
6161

62-
slint = { version = "1.12", default-features = false, features = [
62+
slint = { version = "1.14", default-features = false, features = [
6363
"accessibility",
6464
"backend-winit",
6565
"compat-1-2",
@@ -79,5 +79,5 @@ winit = "0.30"
7979
build-target = "0.8"
8080
git-version = "0.3"
8181
rustc_version = "0.4"
82-
slint-build = "1.12"
82+
slint-build = "1.14"
8383
winres = "0.1"

crates/desktop/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ chrono = { workspace = true }
4040
reqwest = { workspace = true }
4141
serde = { workspace = true }
4242
serde_json = { workspace = true }
43+
thiserror = { workspace = true }
4344
tokio-tungstenite = { workspace = true }
4445
tokio-util = { workspace = true }
4546
tower-http = { workspace = true }

crates/desktop/src/bridges/ui_state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub fn setup(window: &MainWindow) {
1515
name: "localhost".into(),
1616
state: "pending".into(),
1717
features: "".into(),
18+
settings: "{}".into(),
1819
});
1920

2021
let window_weak = window_weak.clone();

crates/desktop/src/bridges/window_control.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22

3-
use i_slint_backend_winit::{WinitWindowAccessor, WinitWindowEventResult};
3+
use i_slint_backend_winit::{EventResult, WinitWindowAccessor};
44
use slint::ComponentHandle;
55
use winit::window::ResizeDirection;
66

@@ -33,13 +33,13 @@ pub fn setup(window: &MainWindow) {
3333
if window.get_main_window_minimized() != w.is_minimized() {
3434
window.set_main_window_minimized(w.is_minimized());
3535
}
36-
WinitWindowEventResult::Propagate
36+
EventResult::Propagate
3737
}
3838
winit::event::WindowEvent::CloseRequested => {
3939
launcher::shutdown(&window_weak);
40-
WinitWindowEventResult::PreventDefault
40+
EventResult::PreventDefault
4141
}
42-
_ => WinitWindowEventResult::Propagate,
42+
_ => EventResult::Propagate,
4343
}
4444
});
4545

crates/desktop/src/daemon/api_controller.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ async fn launch_instance(
199199

200200
tokio::spawn(async move {
201201
let client = reqwest::Client::new();
202-
update_instance_latency(state_clone, instance, &client).await;
202+
update_instance_latency(state_clone, instance, &client)
203+
.await
204+
.ok();
203205
});
204206

205207
match slint::invoke_from_event_loop(move || {
@@ -335,10 +337,16 @@ async fn request_control(
335337
let json_body = axum::Json::<ScopeData>::from_request(req, &state)
336338
.await
337339
.ok();
338-
let (scope_name, scope_features) = if let Some(json_body) = json_body {
339-
(json_body.name.clone(), json_body.features)
340+
let (scope_name, scope_features, scope_settings) = if let Some(Json(ScopeData {
341+
name,
342+
features,
343+
settings,
344+
..
345+
})) = json_body
346+
{
347+
(name, features, settings)
340348
} else {
341-
(req_scope.clone(), FeatureFlags::Basic)
349+
(req_scope.clone(), FeatureFlags::Basic, Default::default())
342350
};
343351

344352
let mut scopes = state.scopes.write().await;
@@ -355,6 +363,7 @@ async fn request_control(
355363
host: req_scope.clone(),
356364
state: "pending".to_string(),
357365
features: scope_features,
366+
settings: scope_settings.clone(),
358367
};
359368
scopes.push(scope);
360369

@@ -371,6 +380,9 @@ async fn request_control(
371380
name: scope_name.into(),
372381
state: "pending".into(),
373382
features: scope_features.to_shared_string(),
383+
settings: serde_json::to_string(&scope_settings)
384+
.unwrap_or("{}".to_string())
385+
.into(),
374386
};
375387
scopes.push(scope);
376388
}) {
@@ -420,6 +432,9 @@ async fn update_website_info(
420432
name: scope_data.name.clone().into(),
421433
state: state_d.into(),
422434
features: scope_data.features.to_shared_string(),
435+
settings: serde_json::to_string(&scope_data.settings)
436+
.unwrap_or("{}".to_string())
437+
.into(),
423438
};
424439
scopes.set_row_data(index, scope);
425440
}) {

crates/desktop/src/daemon/latency_worker.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use reqwest::Method;
22
use slint::{ComponentHandle, Model, VecModel};
3-
use tracing::debug;
3+
use thiserror::Error;
4+
use tracing::{debug, warn};
45

56
use super::{
67
model::{FeatureFlags, InstanceData, ServerState},
@@ -25,11 +26,10 @@ pub async fn start(state: ServerState) {
2526
let client = client.clone();
2627
let state = state.clone();
2728
tokio::spawn(async move {
28-
if update_instance_latency(state.clone(), instance.clone(), &client)
29-
.await
30-
.is_none()
31-
{
32-
pingfall(state, instance).await;
29+
let result =
30+
update_instance_latency(state.clone(), instance.clone(), &client).await;
31+
if let Err(e) = result {
32+
pingfall(state.clone(), instance.clone(), e).await;
3333
}
3434
});
3535
}
@@ -40,25 +40,32 @@ pub async fn start(state: ServerState) {
4040
}
4141
}
4242

43+
#[derive(Debug, Error)]
44+
pub enum LatencyError {
45+
#[error("Request error: {0}")]
46+
Rewqest(#[from] reqwest::Error),
47+
#[error("Non-success status code")]
48+
NonSuccessStatus(u16),
49+
}
50+
4351
pub async fn update_instance_latency(
4452
state: ServerState, instance: InstanceData, client: &reqwest::Client,
45-
) -> Option<i32> {
53+
) -> Result<i32, LatencyError> {
4654
let req = client
4755
.request(Method::OPTIONS, instance.remote.replace("ws", "http"))
4856
.header("User-Agent", format!("wsrx/{}", env!("CARGO_PKG_VERSION")))
49-
.build()
50-
.ok()?;
57+
.build()?;
5158

5259
let start_time = std::time::Instant::now();
5360

54-
let resp = client.execute(req).await.ok()?;
61+
let resp = client.execute(req).await?;
5562

5663
let elapsed = if resp.status().is_success() {
5764
// always > 0
5865
start_time.elapsed().as_millis() as i32 / 2
5966
} else {
6067
debug!("Failed to ping instance: {}", resp.status());
61-
return None;
68+
return Err(LatencyError::NonSuccessStatus(resp.status().as_u16()));
6269
};
6370

6471
for proxy_instance in state.instances.write().await.iter_mut() {
@@ -98,10 +105,10 @@ pub async fn update_instance_latency(
98105
break;
99106
}
100107

101-
Some(elapsed)
108+
Ok(elapsed)
102109
}
103110

104-
async fn pingfall(state: ServerState, instance: InstanceData) {
111+
async fn pingfall(state: ServerState, instance: InstanceData, err: LatencyError) {
105112
let scopes = state.scopes.read().await;
106113

107114
let scope = scopes
@@ -111,6 +118,33 @@ async fn pingfall(state: ServerState, instance: InstanceData) {
111118
if let Some(scope) = scope
112119
&& scope.features.contains(FeatureFlags::PingFall)
113120
{
114-
on_instance_del(&state, &instance.local).await;
121+
let settings = scope.settings.get(&FeatureFlags::PingFall);
122+
if let Some(settings) = settings {
123+
let pingfall_settings: super::model::PingFallSettings =
124+
serde_json::from_value(settings.to_owned()).unwrap_or_default();
125+
126+
match err {
127+
LatencyError::NonSuccessStatus(code) => {
128+
if pingfall_settings.fail_status.contains(&code)
129+
|| pingfall_settings.fail_status.is_empty()
130+
{
131+
warn!(
132+
"PingFall triggered for instance {} due to status code {}",
133+
instance.local, code
134+
);
135+
on_instance_del(&state, &instance.local).await;
136+
}
137+
}
138+
LatencyError::Rewqest(_) => {
139+
if pingfall_settings.drop_unknown {
140+
warn!(
141+
"PingFall triggered for instance {} due to request error",
142+
instance.local
143+
);
144+
on_instance_del(&state, &instance.local).await;
145+
}
146+
}
147+
}
148+
}
115149
}
116150
}

crates/desktop/src/daemon/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub fn setup(ui: &MainWindow) {
7878
name: scope.name.clone().into(),
7979
state: scope.state.clone().into(),
8080
features: scope.features.to_shared_string(),
81+
settings: serde_json::to_string(&scope.settings)
82+
.unwrap_or("{}".to_string())
83+
.into(),
8184
});
8285
}
8386
let scoped_instances: Rc<VecModel<Instance>> = Rc::new(VecModel::default());
@@ -241,6 +244,7 @@ pub fn save_scopes(ui: &slint::Weak<MainWindow>) {
241244
.split(",")
242245
.map(|s| s.trim().to_string())
243246
.into(),
247+
settings: serde_json::from_str(scope.settings.to_string().as_str()).unwrap_or_default(),
244248
});
245249
}
246250
let proj_dirs = match ProjectDirs::from("org", "xdsec", "wsrx") {

crates/desktop/src/daemon/model.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::{fmt::Display, sync::Arc};
1+
use std::{collections::HashMap, fmt::Display, sync::Arc};
22

33
use bitflags::bitflags;
44
use serde::{Deserialize, Serialize};
5+
use serde_json::Value;
56
use tokio::{net::TcpListener, sync::RwLock};
67
use wsrx::tunnel::Tunnel;
78

@@ -28,6 +29,8 @@ pub struct ScopeData {
2829
pub name: String,
2930
pub state: String,
3031
pub features: FeatureFlags,
32+
#[serde(default)]
33+
pub settings: HashMap<FeatureFlags, Value>,
3134
}
3235

3336
#[derive(Clone)]
@@ -46,6 +49,16 @@ bitflags! {
4649
}
4750
}
4851

52+
#[allow(dead_code)]
53+
#[derive(Debug, Clone, Serialize, Deserialize)]
54+
pub struct BasicSettings {}
55+
56+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
57+
pub struct PingFallSettings {
58+
pub fail_status: Vec<u16>,
59+
pub drop_unknown: bool,
60+
}
61+
4962
const FEATURE_MAP: &[(&str, FeatureFlags)] = &[
5063
("basic", FeatureFlags::Basic),
5164
("pingfall", FeatureFlags::PingFall),

crates/desktop/src/daemon/ui_controller.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ pub async fn on_instance_add(state: &ServerState, remote: &str, local: &str) {
4444

4545
tokio::spawn(async move {
4646
let client = reqwest::Client::new();
47-
update_instance_latency(state_clone, instance_data, &client).await;
47+
update_instance_latency(state_clone, instance_data, &client)
48+
.await
49+
.ok();
4850
});
4951

5052
let label = instance.label.clone();
@@ -120,11 +122,13 @@ pub async fn on_scope_allow(state: &ServerState, ui: slint::Weak<MainWindow>, sc
120122
let mut scopes = state.scopes.write().await;
121123
let scope_name;
122124
let scope_features;
125+
let scope_settings;
123126

124127
if let Some(scope) = scopes.iter_mut().find(|s| s.host == scope_host) {
125128
scope.state = "allowed".to_string();
126129
scope_name = scope.name.clone();
127130
scope_features = scope.features;
131+
scope_settings = scope.settings.clone();
128132
} else {
129133
return;
130134
}
@@ -151,6 +155,9 @@ pub async fn on_scope_allow(state: &ServerState, ui: slint::Weak<MainWindow>, sc
151155
name: scope_name.into(),
152156
state: "allowed".into(),
153157
features: scope_features.to_shared_string(),
158+
settings: serde_json::to_string(&scope_settings)
159+
.unwrap_or("{}".to_string())
160+
.into(),
154161
},
155162
);
156163
}

crates/desktop/ui/blocks/bridges.slint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export struct Scope {
3838
name: string,
3939
state: string,
4040
features: string,
41+
settings: string,
4142
}
4243

4344
export global InstanceBridge {

0 commit comments

Comments
 (0)