feat(gui): показывать адрес для других устройств в LAN-режиме (#40)

Второй человек за неделю не смог подключить телефон, потому что адрес негде
взять. В #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>
This commit is contained in:
Никита Sonic
2026-08-09 18:50:02 +03:00
committed by GitHub
parent a571b4289a
commit 1a56e1c96e
3 changed files with 135 additions and 0 deletions
+28
View File
@@ -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 {
<span>порт ${status.port}</span>
</div>
${status.shareAddress ? `
<div class="share-card">
<div class="share-label">Адрес для других устройств</div>
<button id="copy-address" class="share-address" title="Нажмите, чтобы скопировать">
${escapeHtml(status.shareAddress)}
</button>
<div class="share-hint">
Впишите его в Telegram на телефоне. Не <code>127.0.0.1</code> — на другом
устройстве это означает само устройство.
</div>
</div>` : ""}
<nav class="bottom-nav" aria-label="Разделы">
<button id="settings-nav" class="nav-button">
<span class="nav-icon">${icons.settings}</span>
@@ -162,6 +177,7 @@ function renderHome(): void {
`, "home-page");
document.querySelector("#power")?.addEventListener("click", toggleProtection);
document.querySelector("#copy-address")?.addEventListener("click", copyShareAddress);
document.querySelector("#settings-nav")?.addEventListener("click", () => navigate("settings"));
document.querySelector("#diagnostics-nav")?.addEventListener("click", () => navigate("diagnostics"));
}
@@ -368,6 +384,18 @@ async function saveSettings(event: Event): Promise<void> {
}
}
async function copyShareAddress(): Promise<void> {
const address = status.shareAddress;
if (!address) return;
try {
await navigator.clipboard.writeText(address);
showToast("Адрес скопирован");
} catch {
// Буфер обмена может быть недоступен — адрес и так виден на экране.
showToast("Скопируйте адрес вручную", true);
}
}
function showToast(message: string, error = false): void {
window.clearTimeout(toastTimer);
window.requestAnimationFrame(() => {
+50
View File
@@ -847,3 +847,53 @@ input:disabled {
0 19px 52px rgba(9, 182, 133, 0.4);
}
}
/* Адрес для подключения других устройств. Показывается только в LAN-режиме:
люди искали его в интерфейсе, не находили и вписывали 127.0.0.1, который на
телефоне означает сам телефон (by-sonic/tglock#36). */
.share-card {
margin-top: 14px;
padding: 14px 16px;
border-radius: 14px;
background: rgba(42, 171, 238, 0.08);
border: 1px solid rgba(42, 171, 238, 0.24);
text-align: center;
}
.share-label {
font-size: 12px;
letter-spacing: 0.02em;
color: var(--text-muted, #8b949e);
}
.share-address {
display: block;
width: 100%;
margin: 6px 0 8px;
padding: 6px 8px;
border: 0;
border-radius: 10px;
background: transparent;
color: var(--accent, #2aabee);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 20px;
font-weight: 600;
cursor: pointer;
transition: background 150ms ease;
}
.share-address:hover,
.share-address:focus-visible {
background: rgba(42, 171, 238, 0.14);
}
.share-hint {
font-size: 11px;
line-height: 1.45;
color: var(--text-muted, #8b949e);
}
.share-hint code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 11px;
}