harden telegram webapp production readiness

This commit is contained in:
VPN SaaS Dev
2026-05-12 19:14:21 +09:00
parent e75697f83e
commit 2ba2e88432
27 changed files with 931 additions and 155 deletions

View File

@@ -16,9 +16,9 @@
<div class="auth-panel">
<p class="eyebrow">Drivers</p>
<h1>Гараж</h1>
<p>Войди через Telegram, чтобы привязать гараж к твоему chat_id.</p>
<p id="authMessage">Это приложение открывается через Telegram-бота. Откройте Mini App из Telegram.</p>
<div id="telegramLoginSlot" class="telegram-login-slot"></div>
<a id="telegramLoginLink" class="telegram-login-link hidden" href="#" target="_blank" rel="noreferrer">Войти через Telegram</a>
<a id="telegramLoginLink" class="telegram-login-link hidden" href="#" target="_blank" rel="noreferrer">Открыть бота</a>
</div>
</div>
<main class="shell">
@@ -102,8 +102,8 @@
<strong>ТО / ремонт</strong>
</button>
<button class="action-card" data-action="scan">
<span>Скан чека</span>
<strong>OCR</strong>
<span>Разбор чека</span>
<strong>Текст</strong>
</button>
</section>
@@ -220,7 +220,7 @@
<button class="menu-row" id="openCarProfileBtn">Параметры автомобиля</button>
<button class="menu-row" id="openSettingsBtn">Локаль и валюта</button>
<button class="menu-row" id="openNotificationsBtn">Уведомления</button>
<button class="menu-row" id="openScanBtn">Сканировать чек</button>
<button class="menu-row" id="openScanBtn">Разобрать чек</button>
<section class="drawer-section hidden" id="settingsSection">
<h2>Настройки</h2>
@@ -357,7 +357,7 @@
<div class="scan-modal hidden" id="scanModal">
<div class="scan-panel">
<div class="section-head">
<h2>Скан чека</h2>
<h2>Разбор чека</h2>
<button class="icon-btn" id="closeScanBtn" aria-label="Закрыть">×</button>
</div>
<form id="ocrForm" class="scan-form">
@@ -368,9 +368,9 @@
<input id="receiptCameraInput" class="hidden-file" type="file" accept="image/*" capture="environment" />
<input id="receiptFileInput" class="hidden-file" type="file" accept="image/*,.pdf,.txt" />
<div id="receiptFileName" class="file-hint">Файл не выбран</div>
<button type="submit">Распознать</button>
<button type="submit">Разобрать текст</button>
</form>
<div id="ocrResult" class="tip-card">После распознавания поля заправки заполнятся автоматически.</div>
<div id="ocrResult" class="tip-card">Сейчас разбираем текстовые чеки. OCR по фото будет подключен отдельно.</div>
</div>
</div>

View File

@@ -408,8 +408,13 @@ function formData(form) {
}
async function api(path, options = {}) {
const headers = { "Content-Type": "application/json", ...(options.headers || {}) };
if (tg?.initData) headers["X-Telegram-Init-Data"] = tg.initData;
if (!tg?.initData && state.authConfig?.allow_dev_auth) {
headers["X-Dev-Telegram-Id"] = localStorage.getItem("driversDevTelegramId") || "1";
}
const response = await fetch(`/api${path}`, {
headers: { "Content-Type": "application/json", ...(options.headers || {}) },
headers,
...options,
});
if (!response.ok) {
@@ -497,13 +502,14 @@ async function ensureUser() {
hideAuthOverlay();
return;
}
const stored = localStorage.getItem("driversUser");
if (stored) {
state.user = JSON.parse(stored);
if (state.authConfig?.allow_dev_auth) {
const devId = localStorage.getItem("driversDevTelegramId") || "1";
localStorage.setItem("driversDevTelegramId", devId);
state.user = await api("/users/me");
hideAuthOverlay();
return;
}
await showTelegramLogin();
showTelegramOpenHint();
throw new Error("Требуется вход через Telegram");
}
@@ -512,22 +518,33 @@ function hideAuthOverlay() {
document.body.classList.remove("auth-required");
}
async function showTelegramLogin() {
function showTelegramOpenHint() {
const overlay = document.querySelector("#authOverlay");
const slot = document.querySelector("#telegramLoginSlot");
const link = document.querySelector("#telegramLoginLink");
const message = document.querySelector("#authMessage");
overlay?.classList.remove("hidden");
document.body.classList.add("auth-required");
if (!slot || slot.dataset.ready) return;
const botUsername = state.authConfig?.bot_username;
if (message) {
message.textContent = "Это приложение открывается через Telegram-бота. Откройте Mini App из Telegram.";
}
if (slot) slot.textContent = "";
if (!botUsername) {
slot.textContent = "Telegram Login временно недоступен";
return;
}
if (link) {
link.href = `https://t.me/${botUsername}?start=web_login`;
link.href = `https://t.me/${botUsername}`;
link.classList.remove("hidden");
}
}
async function showTelegramLogin() {
showTelegramOpenHint();
const slot = document.querySelector("#telegramLoginSlot");
if (!slot || slot.dataset.ready) return;
const botUsername = state.authConfig?.bot_username;
if (!botUsername) return;
window.onTelegramAuth = async (user) => {
state.user = await api("/users/telegram-login", {
method: "POST",
@@ -1424,7 +1441,11 @@ document.querySelector("#ocrForm").addEventListener("submit", async (event) => {
await runAction(formButton, "Распознаю чек...", async () => {
const payload = new FormData();
payload.append("file", file);
const response = await fetch("/api/ocr/fuel-receipt", { method: "POST", body: payload });
const response = await fetch("/api/ocr/parse-text-receipt", {
method: "POST",
headers: tg?.initData ? { "X-Telegram-Init-Data": tg.initData } : {},
body: payload,
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
document.querySelector("#ocrResult").textContent = `${result.message} ${Math.round((result.confidence || 0) * 100)}%`;