fix: refresh mini app login state
Deploy Telegram Bot / build-and-push (push) Successful in 4m23s
Deploy Telegram Bot / deploy (push) Successful in 12s

This commit is contained in:
2026-04-28 17:03:53 +03:00
parent 41f2ea6e90
commit 8220f2060f
10 changed files with 102 additions and 15 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- main - main
env: env:
VERSION: 1.9.0 VERSION: 1.9.1
jobs: jobs:
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
+1 -1
View File
@@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>1.9.0</Version> <Version>1.9.1</Version>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion> <LangVersion>preview</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
+2 -2
View File
@@ -4,7 +4,7 @@
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire. Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
**Текущая версия:** `v1.9.0`. **Текущая версия:** `v1.9.1`.
--- ---
@@ -24,7 +24,7 @@
### 🌐 Web Dashboard (Blazor Server) ### 🌐 Web Dashboard (Blazor Server)
- **🔐 Авторизация через Telegram**: Безопасный вход с использованием Telegram Login Widget (HMAC-SHA256 валидация). - **🔐 Авторизация через Telegram**: Безопасный вход с использованием Telegram Login Widget (HMAC-SHA256 валидация).
- **📱 Telegram Mini App Dashboard**: Мобильная версия dashboard открывается прямо из Telegram, проверяет WebApp `initData` на сервере и использует те же права owner/co-GM, что и обычный Web Dashboard. - **📱 Telegram Mini App Dashboard**: Мобильная версия dashboard открывается прямо из Telegram, проверяет WebApp `initData` на сервере и использует те же права owner/co-GM, что и обычный Web Dashboard. Mini App ждёт данные Telegram при старте и автоматически обновляет состояние входа после внешнего Telegram Login.
- **📝 Удобное редактирование**: Веб-интерфейс для детального редактирования сессий, изменения дат, названий и статусов. - **📝 Удобное редактирование**: Веб-интерфейс для детального редактирования сессий, изменения дат, названий и статусов.
- **🤝 Co-GM и делегирование**: Owner группы назначает помощников по Telegram ID, а co-GM получает доступ к управлению расписанием в Telegram и Web Dashboard. - **🤝 Co-GM и делегирование**: Owner группы назначает помощников по Telegram ID, а co-GM получает доступ к управлению расписанием в Telegram и Web Dashboard.
- **📋 Шаблоны кампаний**: Owner и co-GM управляют типовыми параметрами кампаний в отдельной вкладке `Шаблоны`, а на странице группы запускают новый повторяющийся batch из выбранного шаблона. - **📋 Шаблоны кампаний**: Owner и co-GM управляют типовыми параметрами кампаний в отдельной вкладке `Шаблоны`, а на странице группы запускают новый повторяющийся batch из выбранного шаблона.
+2 -2
View File
@@ -17,7 +17,7 @@ services:
retries: 10 retries: 10
bot: bot:
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.0 image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.1
restart: always restart: always
depends_on: depends_on:
db: db:
@@ -30,7 +30,7 @@ services:
- gmrelay - gmrelay
web: web:
image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.0 image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.1
restart: always restart: always
depends_on: depends_on:
db: db:
+71 -7
View File
@@ -24,12 +24,30 @@
<ReconnectModal /> <ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script> <script src="@Assets["_framework/blazor.web.js"]"></script>
<script> <script>
(function () { window.waitForTelegramMiniAppInitData = async function (timeoutMs) {
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) { var deadline = Date.now() + (timeoutMs || 3000);
var webApp = window.Telegram.WebApp;
document.body.classList.add('telegram-mini-app'); while (Date.now() <= deadline) {
webApp.ready(); if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) {
return window.Telegram.WebApp;
}
await new Promise(function (resolve) {
setTimeout(resolve, 100);
});
} }
return null;
};
(async function () {
var webApp = await window.waitForTelegramMiniAppInitData(1000);
if (!webApp) {
return;
}
document.body.classList.add('telegram-mini-app');
webApp.ready();
})(); })();
window.loadTelegramWidget = function (botUsername, authUrl) { window.loadTelegramWidget = function (botUsername, authUrl) {
@@ -46,12 +64,58 @@
container.appendChild(script); container.appendChild(script);
}; };
window.watchTelegramMiniAppLogin = function (statusUrl, redirectUrl) {
if (window.gmRelayMiniAppLoginWatcher) {
return;
}
var stopWatching = function () {
if (window.gmRelayMiniAppLoginWatcher) {
window.clearInterval(window.gmRelayMiniAppLoginWatcher);
window.gmRelayMiniAppLoginWatcher = null;
}
};
var checkLogin = async function () {
try {
var response = await fetch(statusUrl, {
credentials: 'same-origin',
cache: 'no-store'
});
if (!response.ok) {
return;
}
var payload = await response.json();
if (payload.authenticated) {
stopWatching();
window.location.href = redirectUrl || '/';
}
} catch (error) {
return;
}
};
window.gmRelayMiniAppLoginWatcher = window.setInterval(checkLogin, 1000);
window.addEventListener('focus', function () {
void checkLogin();
});
document.addEventListener('visibilitychange', function () {
if (!document.hidden) {
void checkLogin();
}
});
void checkLogin();
};
window.authenticateTelegramMiniApp = async function (authUrl, redirectUrl) { window.authenticateTelegramMiniApp = async function (authUrl, redirectUrl) {
if (!window.Telegram || !window.Telegram.WebApp || !window.Telegram.WebApp.initData) { var webApp = await window.waitForTelegramMiniAppInitData(3000);
if (!webApp) {
return false; return false;
} }
var webApp = window.Telegram.WebApp;
document.body.classList.add('telegram-mini-app'); document.body.classList.add('telegram-mini-app');
webApp.ready(); webApp.ready();
webApp.expand(); webApp.expand();
@@ -56,7 +56,7 @@
</button> </button>
</form> </form>
<div class="nav-version">v1.9.0</div> <div class="nav-version">v1.9.1</div>
</div> </div>
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
@@ -58,6 +58,7 @@
statusMessage = "Mini App доступен из Telegram. Для браузера используйте обычный вход."; statusMessage = "Mini App доступен из Telegram. Для браузера используйте обычный вход.";
showFallback = true; showFallback = true;
StateHasChanged(); StateHasChanged();
await TryWatchLoginAsync();
} }
} }
catch (JSException) catch (JSException)
@@ -65,6 +66,18 @@
statusMessage = "Не удалось получить данные Telegram Mini App. Попробуйте открыть dashboard из бота."; statusMessage = "Не удалось получить данные Telegram Mini App. Попробуйте открыть dashboard из бота.";
showFallback = true; showFallback = true;
StateHasChanged(); StateHasChanged();
await TryWatchLoginAsync();
}
}
private async Task TryWatchLoginAsync()
{
try
{
await JS.InvokeVoidAsync("watchTelegramMiniAppLogin", "/auth/status", "/");
}
catch (JSException)
{
} }
} }
} }
+3
View File
@@ -117,6 +117,9 @@ app.MapPost("/auth/telegram-webapp", async (
return Results.Ok(new { redirectUrl = "/" }); return Results.Ok(new { redirectUrl = "/" });
}).DisableAntiforgery(); }).DisableAntiforgery();
app.MapGet("/auth/status", (HttpContext context) =>
Results.Ok(new { authenticated = context.User.Identity?.IsAuthenticated == true }));
app.MapPost("/auth/logout", async (HttpContext context) => app.MapPost("/auth/logout", async (HttpContext context) =>
{ {
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+1 -1
View File
@@ -1,5 +1,5 @@
/* ============================================ /* ============================================
GM-Relay Design System v1.9.0 GM-Relay Design System v1.9.1
Dark RPG Dashboard Theme Dark RPG Dashboard Theme
============================================ */ ============================================ */
@@ -10,6 +10,8 @@ public sealed class MiniAppDashboardTests
Assert.Contains("@page \"/miniapp\"", miniAppPage, StringComparison.Ordinal); Assert.Contains("@page \"/miniapp\"", miniAppPage, StringComparison.Ordinal);
Assert.Contains("authenticateTelegramMiniApp", miniAppPage, StringComparison.Ordinal); Assert.Contains("authenticateTelegramMiniApp", miniAppPage, StringComparison.Ordinal);
Assert.Contains("/auth/telegram-webapp", miniAppPage, StringComparison.Ordinal); Assert.Contains("/auth/telegram-webapp", miniAppPage, StringComparison.Ordinal);
Assert.Contains("watchTelegramMiniAppLogin", miniAppPage, StringComparison.Ordinal);
Assert.Contains("/auth/status", miniAppPage, StringComparison.Ordinal);
} }
[Fact] [Fact]
@@ -20,6 +22,9 @@ public sealed class MiniAppDashboardTests
Assert.Contains("telegram-web-app.js", appShell, StringComparison.Ordinal); Assert.Contains("telegram-web-app.js", appShell, StringComparison.Ordinal);
Assert.Contains("window.authenticateTelegramMiniApp", appShell, StringComparison.Ordinal); Assert.Contains("window.authenticateTelegramMiniApp", appShell, StringComparison.Ordinal);
Assert.Contains("Telegram.WebApp.initData", appShell, StringComparison.Ordinal); Assert.Contains("Telegram.WebApp.initData", appShell, StringComparison.Ordinal);
Assert.Contains("window.waitForTelegramMiniAppInitData", appShell, StringComparison.Ordinal);
Assert.Contains("window.watchTelegramMiniAppLogin", appShell, StringComparison.Ordinal);
Assert.Contains("setTimeout(resolve, 100)", appShell, StringComparison.Ordinal);
} }
[Fact] [Fact]
@@ -29,6 +34,8 @@ public sealed class MiniAppDashboardTests
Assert.Contains("MapPost(\"/auth/telegram-webapp\"", program, StringComparison.Ordinal); Assert.Contains("MapPost(\"/auth/telegram-webapp\"", program, StringComparison.Ordinal);
Assert.Contains("VerifyWebAppInitData", program, StringComparison.Ordinal); Assert.Contains("VerifyWebAppInitData", program, StringComparison.Ordinal);
Assert.Contains("MapGet(\"/auth/status\"", program, StringComparison.Ordinal);
Assert.Contains("authenticated", program, StringComparison.Ordinal);
} }
[Fact] [Fact]