Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57c8714889 | |||
| 8220f2060f |
@@ -6,7 +6,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
VERSION: 1.9.0
|
||||
VERSION: 1.9.2
|
||||
|
||||
jobs:
|
||||
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>1.9.0</Version>
|
||||
<Version>1.9.2</Version>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
||||
|
||||
**Текущая версия:** `v1.9.0`.
|
||||
**Текущая версия:** `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.
|
||||
- **📱 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 из выбранного шаблона.
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
bot:
|
||||
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.0
|
||||
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.0
|
||||
image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.2
|
||||
restart: always
|
||||
depends_on:
|
||||
db:
|
||||
|
||||
@@ -24,12 +24,30 @@
|
||||
<ReconnectModal />
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
<script>
|
||||
(function () {
|
||||
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) {
|
||||
var webApp = window.Telegram.WebApp;
|
||||
document.body.classList.add('telegram-mini-app');
|
||||
webApp.ready();
|
||||
window.waitForTelegramMiniAppInitData = async function (timeoutMs) {
|
||||
var deadline = Date.now() + (timeoutMs || 3000);
|
||||
|
||||
while (Date.now() <= deadline) {
|
||||
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) {
|
||||
@@ -46,12 +64,108 @@
|
||||
container.appendChild(script);
|
||||
};
|
||||
|
||||
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);
|
||||
window.gmRelayMiniAppLoginWatcher = null;
|
||||
}
|
||||
};
|
||||
|
||||
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',
|
||||
cache: 'no-store'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = await response.json();
|
||||
if (payload.authenticated) {
|
||||
stopWatching();
|
||||
window.location.href = redirectUrl || '/';
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadWhenUnauthenticated) {
|
||||
reloadAfterExternalLogin();
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.gmRelayMiniAppLoginWatcher = window.setInterval(checkLogin, 1000);
|
||||
window.addEventListener('blur', function () {
|
||||
allowNextExternalLoginReload();
|
||||
});
|
||||
window.addEventListener('focus', function () {
|
||||
void checkLogin(true);
|
||||
});
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.hidden) {
|
||||
allowNextExternalLoginReload();
|
||||
return;
|
||||
}
|
||||
|
||||
void checkLogin(true);
|
||||
});
|
||||
|
||||
void checkLogin(false);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var webApp = window.Telegram.WebApp;
|
||||
document.body.classList.add('telegram-mini-app');
|
||||
webApp.ready();
|
||||
webApp.expand();
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="nav-version">v1.9.0</div>
|
||||
<div class="nav-version">v1.9.2</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
if (firstRender)
|
||||
{
|
||||
await JS.InvokeVoidAsync("loadTelegramWidget", BotUsername, AuthUrl);
|
||||
await JS.InvokeVoidAsync("watchTelegramMiniAppLogin", "/auth/status", "/", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
statusMessage = "Mini App доступен из Telegram. Для браузера используйте обычный вход.";
|
||||
showFallback = true;
|
||||
StateHasChanged();
|
||||
await TryWatchLoginAsync();
|
||||
}
|
||||
}
|
||||
catch (JSException)
|
||||
@@ -65,6 +66,18 @@
|
||||
statusMessage = "Не удалось получить данные Telegram Mini App. Попробуйте открыть dashboard из бота.";
|
||||
showFallback = true;
|
||||
StateHasChanged();
|
||||
await TryWatchLoginAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryWatchLoginAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await JS.InvokeVoidAsync("watchTelegramMiniAppLogin", "/auth/status", "/");
|
||||
}
|
||||
catch (JSException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ app.MapPost("/auth/telegram-webapp", async (
|
||||
return Results.Ok(new { redirectUrl = "/" });
|
||||
}).DisableAntiforgery();
|
||||
|
||||
app.MapGet("/auth/status", (HttpContext context) =>
|
||||
Results.Ok(new { authenticated = context.User.Identity?.IsAuthenticated == true }));
|
||||
|
||||
app.MapPost("/auth/logout", async (HttpContext context) =>
|
||||
{
|
||||
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ============================================
|
||||
GM-Relay Design System v1.9.0
|
||||
GM-Relay Design System v1.9.2
|
||||
Dark RPG Dashboard Theme
|
||||
============================================ */
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ public sealed class MiniAppDashboardTests
|
||||
Assert.Contains("@page \"/miniapp\"", miniAppPage, StringComparison.Ordinal);
|
||||
Assert.Contains("authenticateTelegramMiniApp", 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]
|
||||
@@ -20,6 +22,12 @@ public sealed class MiniAppDashboardTests
|
||||
Assert.Contains("telegram-web-app.js", appShell, StringComparison.Ordinal);
|
||||
Assert.Contains("window.authenticateTelegramMiniApp", 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);
|
||||
Assert.Contains("reloadOnReturn", appShell, StringComparison.Ordinal);
|
||||
Assert.Contains("gmRelayMiniAppLoginLeftPage", appShell, StringComparison.Ordinal);
|
||||
Assert.Contains("window.location.reload()", appShell, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -29,6 +37,8 @@ public sealed class MiniAppDashboardTests
|
||||
|
||||
Assert.Contains("MapPost(\"/auth/telegram-webapp\"", program, StringComparison.Ordinal);
|
||||
Assert.Contains("VerifyWebAppInitData", program, StringComparison.Ordinal);
|
||||
Assert.Contains("MapGet(\"/auth/status\"", program, StringComparison.Ordinal);
|
||||
Assert.Contains("authenticated", program, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,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);
|
||||
|
||||
Reference in New Issue
Block a user