diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 139abe6..fa96afc 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 1.9.1 + VERSION: 1.9.2 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index f3870ca..c7f7e05 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.9.1 + 1.9.2 net10.0 preview enable diff --git a/README.md b/README.md index a8bdb4d..e279564 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire. -**Текущая версия:** `v1.9.1`. +**Текущая версия:** `v1.9.2`. --- @@ -24,7 +24,7 @@ ### 🌐 Web Dashboard (Blazor Server) - **🔐 Авторизация через Telegram**: Безопасный вход с использованием Telegram Login Widget (HMAC-SHA256 валидация). -- **📱 Telegram Mini App Dashboard**: Мобильная версия dashboard открывается прямо из Telegram, проверяет WebApp `initData` на сервере и использует те же права owner/co-GM, что и обычный Web Dashboard. Mini App ждёт данные Telegram при старте и автоматически обновляет состояние входа после внешнего Telegram Login. +- **📱 Telegram Mini App Dashboard**: Мобильная версия dashboard открывается прямо из Telegram, проверяет WebApp `initData` на сервере и использует те же права owner/co-GM, что и обычный Web Dashboard. Mini App ждёт данные Telegram при старте и автоматически обновляет состояние входа после внешнего Telegram Login, включая возврат на страницу `/login`. - **📝 Удобное редактирование**: Веб-интерфейс для детального редактирования сессий, изменения дат, названий и статусов. - **🤝 Co-GM и делегирование**: Owner группы назначает помощников по Telegram ID, а co-GM получает доступ к управлению расписанием в Telegram и Web Dashboard. - **📋 Шаблоны кампаний**: Owner и co-GM управляют типовыми параметрами кампаний в отдельной вкладке `Шаблоны`, а на странице группы запускают новый повторяющийся batch из выбранного шаблона. diff --git a/compose.yaml b/compose.yaml index b2904a2..ab1e00a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -17,7 +17,7 @@ services: retries: 10 bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.1 + image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.2 restart: always depends_on: db: @@ -30,7 +30,7 @@ services: - gmrelay web: - image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.1 + image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.2 restart: always depends_on: db: diff --git a/src/GmRelay.Web/Components/App.razor b/src/GmRelay.Web/Components/App.razor index b49d1fb..2b81d66 100644 --- a/src/GmRelay.Web/Components/App.razor +++ b/src/GmRelay.Web/Components/App.razor @@ -64,11 +64,16 @@ container.appendChild(script); }; - window.watchTelegramMiniAppLogin = function (statusUrl, redirectUrl) { + window.watchTelegramMiniAppLogin = function (statusUrl, redirectUrl, reloadOnReturn) { + window.gmRelayMiniAppLoginReloadOnReturn = + window.gmRelayMiniAppLoginReloadOnReturn || reloadOnReturn === true; + if (window.gmRelayMiniAppLoginWatcher) { return; } + window.gmRelayMiniAppLoginLeftPage = false; + var stopWatching = function () { if (window.gmRelayMiniAppLoginWatcher) { window.clearInterval(window.gmRelayMiniAppLoginWatcher); @@ -76,7 +81,41 @@ } }; - var checkLogin = async function () { + var reloadAfterExternalLogin = function () { + if (!window.gmRelayMiniAppLoginReloadOnReturn || !window.gmRelayMiniAppLoginLeftPage) { + return; + } + + window.gmRelayMiniAppLoginLeftPage = false; + + try { + var refreshKey = 'gmrelay-miniapp-login-refresh:' + window.location.pathname; + if (window.sessionStorage && window.sessionStorage.getItem(refreshKey) === '1') { + return; + } + + if (window.sessionStorage) { + window.sessionStorage.setItem(refreshKey, '1'); + } + } catch (error) { + } + + window.location.reload(); + }; + + var allowNextExternalLoginReload = function () { + window.gmRelayMiniAppLoginLeftPage = true; + + try { + var refreshKey = 'gmrelay-miniapp-login-refresh:' + window.location.pathname; + if (window.sessionStorage) { + window.sessionStorage.removeItem(refreshKey); + } + } catch (error) { + } + }; + + var checkLogin = async function (reloadWhenUnauthenticated) { try { var response = await fetch(statusUrl, { credentials: 'same-origin', @@ -91,6 +130,11 @@ if (payload.authenticated) { stopWatching(); window.location.href = redirectUrl || '/'; + return; + } + + if (reloadWhenUnauthenticated) { + reloadAfterExternalLogin(); } } catch (error) { return; @@ -98,16 +142,22 @@ }; window.gmRelayMiniAppLoginWatcher = window.setInterval(checkLogin, 1000); + window.addEventListener('blur', function () { + allowNextExternalLoginReload(); + }); window.addEventListener('focus', function () { - void checkLogin(); + void checkLogin(true); }); document.addEventListener('visibilitychange', function () { - if (!document.hidden) { - void checkLogin(); + if (document.hidden) { + allowNextExternalLoginReload(); + return; } + + void checkLogin(true); }); - void checkLogin(); + void checkLogin(false); }; window.authenticateTelegramMiniApp = async function (authUrl, redirectUrl) { diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index 72216fa..599c090 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor @@ -56,7 +56,7 @@ - + diff --git a/src/GmRelay.Web/Components/Pages/Login.razor b/src/GmRelay.Web/Components/Pages/Login.razor index 0c11f89..fd60659 100644 --- a/src/GmRelay.Web/Components/Pages/Login.razor +++ b/src/GmRelay.Web/Components/Pages/Login.razor @@ -47,6 +47,7 @@ if (firstRender) { await JS.InvokeVoidAsync("loadTelegramWidget", BotUsername, AuthUrl); + await JS.InvokeVoidAsync("watchTelegramMiniAppLogin", "/auth/status", "/", true); } } } diff --git a/src/GmRelay.Web/wwwroot/app.css b/src/GmRelay.Web/wwwroot/app.css index 5b78f53..2ecc4f6 100644 --- a/src/GmRelay.Web/wwwroot/app.css +++ b/src/GmRelay.Web/wwwroot/app.css @@ -1,5 +1,5 @@ /* ============================================ - GM-Relay Design System v1.9.1 + GM-Relay Design System v1.9.2 Dark RPG Dashboard Theme ============================================ */ diff --git a/tests/GmRelay.Bot.Tests/Web/MiniAppDashboardTests.cs b/tests/GmRelay.Bot.Tests/Web/MiniAppDashboardTests.cs index 26da398..279d6f4 100644 --- a/tests/GmRelay.Bot.Tests/Web/MiniAppDashboardTests.cs +++ b/tests/GmRelay.Bot.Tests/Web/MiniAppDashboardTests.cs @@ -25,6 +25,9 @@ public sealed class MiniAppDashboardTests Assert.Contains("window.waitForTelegramMiniAppInitData", appShell, StringComparison.Ordinal); Assert.Contains("window.watchTelegramMiniAppLogin", appShell, StringComparison.Ordinal); Assert.Contains("setTimeout(resolve, 100)", appShell, StringComparison.Ordinal); + Assert.Contains("reloadOnReturn", appShell, StringComparison.Ordinal); + Assert.Contains("gmRelayMiniAppLoginLeftPage", appShell, StringComparison.Ordinal); + Assert.Contains("window.location.reload()", appShell, StringComparison.Ordinal); } [Fact] @@ -48,6 +51,17 @@ public sealed class MiniAppDashboardTests Assert.Contains("@media (max-width: 768px)", css, StringComparison.Ordinal); } + [Fact] + public async Task LoginPage_ShouldRefreshMiniAppAfterExternalTelegramLogin() + { + var loginPage = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Pages/Login.razor")); + + Assert.Contains( + "JS.InvokeVoidAsync(\"watchTelegramMiniAppLogin\", \"/auth/status\", \"/\", true)", + loginPage, + StringComparison.Ordinal); + } + private static string FindRepositoryFile(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory);