Контекст
27 января. fxTunnel крутится на боевом сервере, пользуюсь каждый день. И начинают всплывать реальные проблемы — те, которые на localhost никогда не увидишь. Главная боль: клиент периодически теряет соединение (Wi-Fi, VPN, смена сети) и переподключается. Переподключение работает — но авторизация падает. Почему? Потому что JWT-токен протух.
Проблема: токен, который умирает не вовремя
JWT (JSON Web Token) — это как пропуск в офис с ограниченным сроком. Мой access-токен живёт 15 минут. Если клиент был подключён 20 минут, отвалился и пытается переподключиться — токен уже мёртв. Сценарий: 1. Клиент подключается с валидным токеном — всё ок 2. Работает 20 минут 3. Wi-Fi моргнул — соединение потеряно 4. Клиент переподключается с тем же токеном 5. Сервер: «токен протух, до свидания» 6. Туннель не поднимается Для пользователя это выглядит как «всё сломалось». Ужасно.
Решение: автоматический refresh
В вебе эта проблема решена давно: пара токенов. Access-токен — короткий (15 мин), для повседневных запросов. Refresh-токен — длинный (7 дней), для обновления access-токена.
Добавил в клиент callback TokenRefresher. Когда сервер отвечает «TOKEN_EXPIRED», клиент не сдаётся — вызывает refresher, получает новый access-токен и повторяет авторизацию. Для пользователя это прозрачно: реконнект произошёл, туннель снова работает.
В GUI refresh-токен хранится в системном keyring, и при каждом реконнекте автоматически обновляет access-токен. Клиент может работать неделями без ручной переавторизации.
Кастомная 404
Когда кто-то заходит на несуществующий поддомен (random.mydomain.com), раньше он видел стандартный nginx 404. Скучно и непрофессионально.
Сделал свою страницу ошибки в стиле проекта — cyber-industrial тема, анимированный фон, брендинг fxTunnel. 243 строки HTML/CSS прямо в Go-коде через go:embed.
go:embed — способ упаковать любые файлы (HTML, CSS, картинки) прямо внутрь Go-бинарника. Не нужен отдельный веб-сервер для статики. Один бинарник содержит всё.
Мелочь? Да. Но именно из таких мелочей складывается впечатление. Когда пользователь видит стилизованную страницу ошибки вместо стандартной — это показывает, что о нём подумали.
Грабли
Порядок проверки токена. Сначала я проверял токен ДО попытки refresh. Протух — сразу отказ. Правильно: сервер возвращает специальный код TOKEN_EXPIRED, клиент ловит и делает refresh. Два round-trip вместо одного, зато надёжно.
go:embed и inline HTML. 243 строки HTML внутри Go-файла — это не красиво. Позже вынес шаблоны в отдельные файлы. Но на первом этапе — и так сойдёт.
Мобильная верстка 404. Первая версия выглядела отлично на десктопе и ужасно на телефоне. Layout shift, overflow — классика. Быстрый фикс в следующем коммите.
Итог
27 января — две фичи, которые делают продукт заметно зрелее: - Auto-refresh JWT — клиент работает неделями без ручного вмешательства - Кастомная 404 — выглядит как часть продукта, а не как ошибка сервера
1feff99 feat(client): add automatic JWT token refresh on reconnect
2b5d08a feat(server): add custom styled 404 error page
В следующей части: полный редизайн GUI-клиента. Почему первый дизайн не устроил и почему переделывать — это нормально.