fix(auth): читать initData из моста Telegram, а не только из кэша SDK

retrieveRawInitData() ищет параметры запуска по цепочке location.href ->
запись performance о навигации -> кэш tapps/launchParams в sessionStorage,
записывая удачный результат обратно. Последние два источника привязаны к
документу, а не к текущему запуску мини-аппы: как только SPA сменила
маршрут и hash с tgWebAppData ушёл из адреса, остаются данные ПЕРВОГО
запуска в этой сессии WebView. На iOS WebView переживает переоткрытия, и
SDK молча отдаёт старую копию.

Бэкенд принимает initData с auth_date не старше 30 суток, поэтому
застрявшая копия рано или поздно пересекает порог и вход отваливается с
«Invalid or expired Telegram authentication data».

Спрашиваем ещё и window.Telegram.WebApp.initData (официальный мост,
telegram-web-app.js из index.html) и берём вариант со свежим auth_date:
какой источник протух, зависит от платформы, «самый свежий» верен всегда.
Все три места чтения initData сведены в один модуль.
This commit is contained in:
Fringg
2026-08-19 02:00:41 +03:00
parent acbffa4e17
commit f81af401f7
6 changed files with 220 additions and 17 deletions

View File

@@ -0,0 +1,63 @@
import { retrieveRawInitData } from '@telegram-apps/sdk-react';
/**
* Сырая строка initData Telegram, прочитанная в момент вызова.
*
* Почему мало одного `retrieveRawInitData()` из @telegram-apps/sdk: он ищет
* параметры запуска по цепочке `location.href` → запись performance о навигации
* → собственный кэш `tapps/launchParams` в sessionStorage, записывая удачный
* результат обратно в кэш. Последние два источника привязаны не к текущему
* запуску мини-аппы, а к документу: как только SPA сменила маршрут и hash с
* `tgWebAppData` ушёл из адреса, остаются запись о навигации исходного
* документа и кэш — оба из ПЕРВОГО запуска в этой сессии WebView. На iOS такой
* WebView переживает переоткрытия мини-аппы, и SDK молча отдаёт initData
* недельной давности, без единой ошибки.
*
* Цена — отказ входа: бэкенд принимает initData с `auth_date` не старше 30
* суток, и застрявшая копия рано или поздно пересекает порог, после чего
* логин отвечает «Invalid or expired Telegram authentication data».
*
* Поэтому спрашиваем ещё и `window.Telegram.WebApp.initData` — его пишет
* официальный мост Telegram (telegram-web-app.js, подключается в index.html),
* и он не завязан на кэш SDK. Какой из источников протух, зависит от платформы
* и способа открытия, поэтому берём не «первый доступный», а тот, у которого
* `auth_date` больше.
*/
export function getTelegramInitData(): string | null {
if (typeof window === 'undefined') return null;
const candidates = [fromTelegramBridge(), fromSdk()].filter((value): value is string =>
Boolean(value),
);
if (candidates.length === 0) return null;
// При равном или неразобранном auth_date побеждает мост: он ближе к
// источнику, чем кэш SDK.
return candidates.reduce((best, candidate) =>
authDateOf(candidate) > authDateOf(best) ? candidate : best,
);
}
function fromTelegramBridge(): string | null {
return window.Telegram?.WebApp?.initData || null;
}
function fromSdk(): string | null {
try {
return retrieveRawInitData() || null;
} catch {
// Вне Telegram параметров запуска нет — это не ошибка.
return null;
}
}
/** Метка времени выдачи initData; 0, если её не удалось разобрать. */
function authDateOf(initData: string): number {
try {
const raw = new URLSearchParams(initData).get('auth_date');
const parsed = Number(raw);
return raw && Number.isFinite(parsed) ? parsed : 0;
} catch {
return 0;
}
}