From 1a56e1c96e677da5c4fea935a84ba0b13a13429b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20Sonic?= Date: Sun, 9 Aug 2026 18:50:02 +0300 Subject: [PATCH] =?UTF-8?q?feat(gui):=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B0=D0=B4=D1=80=D0=B5=D1=81?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D1=85=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=20=D0=B2?= =?UTF-8?q?=20LAN-=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B5=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Второй человек за неделю не смог подключить телефон, потому что адрес негде взять. В #36 @modx-arseniy вписал в Telegram на Android настройки «как в клиентах на винде» — то есть 127.0.0.1, который на другом устройстве означает само это устройство, а не компьютер с прокси. Раньше в README было написано, что адрес показывается в интерфейсе; это оказалось неправдой, и я тогда исправил README вместо приложения. Теперь исправлено приложение. В LAN-режиме на главном экране появляется карточка с адресом вида 192.168.1.7:1080. Нажатие копирует его в буфер. Под адресом — предупреждение про 127.0.0.1, потому что именно туда люди и уходят. Адрес берётся из работающего слушателя, а не из текущих настроек: если человек поменял порт, но не перезапустил прокси, показать надо тот, на котором прокси реально поднят. Логика вынесена в чистую функцию share_address и покрыта тестами: на выключенном прокси и на loopback делиться нечем, в LAN-режиме адрес обязан содержать порт и не быть ни 0.0.0.0, ни 127.0.0.1. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com> --- src/main.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ ui/main.ts | 28 +++++++++++++++++++++++++ ui/styles.css | 50 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/src/main.rs b/src/main.rs index 1ace319..6a4f8f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,13 @@ struct StatusSnapshot { route_failures: u32, uptime_seconds: u64, port: u16, + /// Адрес, который нужно вписать в Telegram на другом устройстве. + /// + /// В LAN-режиме это адрес этого компьютера в локальной сети. Люди искали + /// его в интерфейсе и не находили: вписывали `127.0.0.1`, который на + /// телефоне или в эмуляторе означает само устройство, и подключение не + /// работало (by-sonic/tglock#36). + share_address: Option, logs: Vec, } @@ -56,6 +63,10 @@ struct AppState { stats: Arc, settings: Mutex, active_port: Mutex, + /// Слушатель работающего прокси. Нужен, чтобы показать адрес для других + /// устройств именно тот, на котором прокси реально поднят, а не тот, что + /// сейчас выбран в настройках. + active_listen: Mutex>, started_at: Mutex>, logs: Arc>>, settings_path: PathBuf, @@ -71,6 +82,7 @@ impl AppState { stats: proxy::Stats::new(), settings: Mutex::new(settings), active_port: Mutex::new(proxy::DEFAULT_PORT), + active_listen: Mutex::new(None), started_at: Mutex::new(None), logs: Arc::new(Mutex::new(Vec::new())), settings_path, @@ -106,6 +118,7 @@ impl AppState { .unwrap() .map_or(0, |started| started.elapsed().as_secs()), port: *self.active_port.lock().unwrap(), + share_address: share_address(*self.active_listen.lock().unwrap()), logs: self.logs.lock().unwrap().clone(), } } @@ -122,6 +135,18 @@ impl AppState { } } +/// Адрес, который нужно вписать в Telegram на другом устройстве. +/// +/// Только для слушателя на `0.0.0.0`: на loopback делиться нечем, туда никто +/// извне не достучится. Возвращается адрес этой машины в сети, а не `0.0.0.0` +/// и не `127.0.0.1` — последний на телефоне или в эмуляторе означает само +/// устройство, и именно на этом спотыкались (by-sonic/tglock#36). +fn share_address(listen: Option) -> Option { + listen + .filter(|listen| listen.addr.ip().is_unspecified()) + .map(|listen| format!("{}:{}", listen.advertised_host(), listen.addr.port())) +} + fn current_time() -> String { let seconds = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -177,6 +202,8 @@ fn start_proxy(state: State<'_, AppState>) -> Result { ListenConfig::loopback(settings.port) }; + *state.active_listen.lock().unwrap() = Some(listen); + let stats = state.stats.clone(); let logs = state.logs.clone(); std::thread::spawn(move || { @@ -194,6 +221,7 @@ fn start_proxy(state: State<'_, AppState>) -> Result { std::thread::sleep(std::time::Duration::from_millis(220)); if !state.stats.running.load(Ordering::SeqCst) { + *state.active_listen.lock().unwrap() = None; *state.started_at.lock().unwrap() = None; return Err(state .logs @@ -213,6 +241,7 @@ fn start_proxy(state: State<'_, AppState>) -> Result { #[tauri::command] fn stop_proxy(state: State<'_, AppState>) -> StatusSnapshot { state.stats.stop(); + *state.active_listen.lock().unwrap() = None; *state.started_at.lock().unwrap() = None; state.log("Защита выключена", false); state.snapshot() @@ -311,6 +340,34 @@ fn main() { mod tests { use super::*; + #[test] + fn nothing_to_share_when_the_proxy_is_off_or_local() { + assert_eq!( + share_address(None), + None, + "выключенный прокси нечего делить" + ); + assert_eq!( + share_address(Some(ListenConfig::loopback(1080))), + None, + "на loopback снаружи никто не подключится" + ); + } + + #[test] + fn lan_mode_shares_a_reachable_address() { + let shown = share_address(Some(ListenConfig::lan(1443))).expect("в LAN-режиме адрес нужен"); + assert!(shown.ends_with(":1443"), "порт должен быть виден: {shown}"); + assert!( + !shown.starts_with("0.0.0.0"), + "0.0.0.0 нельзя вписать в Telegram: {shown}" + ); + assert!( + !shown.starts_with("127.0.0.1"), + "127.0.0.1 на другом устройстве означает само устройство: {shown}" + ); + } + #[test] fn software_rendering_is_requested_by_default() { let vars = software_rendering_vars(false, |_| false); diff --git a/ui/main.ts b/ui/main.ts index 5d575a3..fc03cf0 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -13,6 +13,8 @@ type Status = { routeFailures: number; uptimeSeconds: number; port: number; + /// Адрес для других устройств. Приходит только в LAN-режиме. + shareAddress: string | null; logs: LogLine[]; }; @@ -41,6 +43,7 @@ let status: Status = { routeFailures: 0, uptimeSeconds: 0, port: 1080, + shareAddress: null, logs: [], }; let settings: Settings = { lanMode: false, port: 1080, workerDomain: "" }; @@ -147,6 +150,18 @@ function renderHome(): void { порт ${status.port} + ${status.shareAddress ? ` + ` : ""} +