Telegram CloudStorage token recovery (resilience against WebView localStorage wipes):
- mirror the refresh token to per-user CloudStorage on login, remove it on logout
- restoreRefreshTokenFromCloud() recovers the refresh token in initialize() when
localStorage is empty, then the normal refresh flow re-establishes the session
- move the initialize() bootstrap call from auth.ts module-load into main.tsx after
the Telegram SDK init(), since launch params + CloudStorage are unavailable before init
- best-effort: no-ops outside Telegram and on any error -> falls back to prior behavior
Accessibility:
- role=alertdialog + aria-modal + aria-labelledby + focus management (useFocusTrap)
on Maintenance, Blacklisted, ChannelSubscription and AccountDeleted blocking screens
- aria-label on ColorPicker Hue/Saturation/Lightness range inputs
Modal a11y:
- focus-trap + role=dialog/aria-modal/aria-labelledby on AdminRoles delete confirm
and AdminBanSystem user-detail modal (+ close aria-label)
Telegram theme/language sync (first run only, explicit choice always wins):
- getTelegramColorScheme() / getTelegramLanguageCode() helpers in useTelegramSDK
- useTheme initial state follows the Telegram client color scheme when no stored theme
- applyTelegramLanguage() adopts the Telegram client language when no stored choice,
called from main.tsx after SDK init (launch params unavailable before init)
@telegram-apps/sdk v3 depends on valibot which uses Object.hasOwn.
On Safari < 15.4 and old Android WebViews this method is missing,
causing LaunchParamsRetrieveError and broken initData auth.
Ref: https://github.com/Telegram-Mini-Apps/tma.js/issues/683
- Fix stale initData comparison in clearStaleSessionIfNeeded that destroyed
valid refresh tokens on mobile WebView reopens
- Restrict X-Telegram-Init-Data header to auth endpoints only
- Close Mini App on auth retry to force fresh initData from Telegram
- Merge Connection page error/not-configured states for better UX
- Remove unnecessary comments across 40+ files (section dividers,
restating comments, noise catch block comments)
- Configure ESLint allowEmptyCatch to properly handle intentional
empty catch blocks (62 warnings resolved)
- Move Google Fonts from CSS @import (render-blocking) to <link> in
index.html with preconnect for earlier discovery
- Remove @import from globals.css — browser no longer waits for CSS
parse -> @import fetch -> font CSS parse before first paint
- Defer initLogoPreload() to requestIdleCallback so logo fetch
doesn't compete with critical resources during page load
Telegram Mini App creates a new WebView on each open, clearing
sessionStorage. Combined with 24h auth_date validation on backend,
returning users couldn't re-authenticate after closing the app.
- Move refresh token to localStorage for persistence across sessions
- Keep access token in sessionStorage (short-lived, OK to lose)
- Fix auth store initialize() to recover when access token is missing
but refresh token exists in localStorage
- Remove separate mountThemeParams() call to fix ConcurrentCallError
(mountMiniApp() handles it internally in SDK v3)
- Update token migration logic for new storage strategy
- Skip token refresh/Bearer header for auth endpoints in request interceptor
- Wait for auth store initialization before attempting Telegram login
- Clear stale session tokens when init data changes (WebView persistence)
- Show backend error details instead of generic messages
- Add /balance/top-up route for payment method selection
- Add /balance/top-up/:methodId route for amount entry and payment
- Remove TopUpModal component (619 lines)
- Simplify InsufficientBalancePrompt to use navigate instead of modals
- Support ?amount and ?returnTo query params for cross-page data flow
- Fix nav isActive to highlight Balance on sub-routes
- Remove unused MainButton platform abstraction
- Increase toast accent opacity for better visibility
При HMR Vite перевыполняет main.tsx, вызывая повторный init() и mount*()
компонентов SDK. mountThemeParams() и другие бросают ConcurrentCallError
асинхронно, что не ловится синхронным try/catch.
Решение: флаг на window.__tg_sdk_initialized предотвращает повторную
инициализацию при горячей перезагрузке модуля.
- Обернуть каждый mount-вызов в safeMountSync() для защиты от
ConcurrentCallError при HMR/повторной инициализации
- Перенести requestFullscreen() внутрь mountViewport().then() — fullscreen
требует смонтированный viewport, ранее вызывался через dynamic import
с setTimeout до завершения mount
- Стабилизировать обработчик BackButton через useRef + useCallback —
navigate меняется каждый рендер, что вызывало постоянную
пере-подписку и потерю обработчика
Полный переход с нативного window.Telegram.WebApp API на @telegram-apps/sdk-react v3.
Bridge SDK оборачивал receiveEvent, из-за чего popup_closed не доходил до нативного
обработчика — это было основной причиной бага с двойным открытием попапа.
Изменения:
- Удалён @telegram-apps/react-router-integration (привязан к SDK v1)
- Инициализация SDK v3 в main.tsx: init(), restoreInitData(), mount всех компонентов
- AppWithNavigator: убран TelegramRouter (initNavigator + useIntegration), все платформы
на BrowserRouter, добавлен TelegramBackButton через SDK v3
- useTelegramSDK: детекция через retrieveLaunchParams(), реактивные значения через
useSignal() (fullscreen, viewport, safe area insets), initData через retrieveRawInitData()
- TelegramAdapter: полная перезапись — попапы через showPopup() (Promise-based, без
ручного onEvent/offEvent/timeout), кнопки через setMainButtonParams/showBackButton,
haptic через hapticFeedbackImpactOccurred, тема через themeParamsState с конвертацией
camelCase→snake_case, cloud storage через getCloudStorageItem/setCloudStorageItem
- api/client.ts: initData через retrieveRawInitData() вместо window.Telegram.WebApp.initData
- AppHeader: фото пользователя через initDataUser() сигнал
- ConnectionModal: openLink через SDK вместо window.Telegram.WebApp.openLink
- ColorPicker: детекция Telegram через isInTelegramWebApp()
- Удалён интерфейс TelegramWebApp (368 строк) и Window augmentation из vite-env.d.ts
- Убрано поле telegram из PlatformContext в types.ts
- Обновлён manualChunks в vite.config.ts
Replace manual BackButton management with official @telegram-apps/react-router-integration
to fix navigation issues (back button cycling and double-click).
Changes:
- Migrate from @tma.js/sdk-react to @telegram-apps/sdk-react
- Downgrade react-router-dom to v6 for package compatibility
- Add AppWithNavigator component with Telegram navigator integration
- Use browser history mode (hashMode: null) to prevent hash URLs
- Add multi-level platform detection (user data, platform, version)
- Add try-catch error handling for navigator attach/detach
- Simplify useBackButton hook to no-op (navigator handles automatically)
- Update vite config with new Telegram packages
The navigator automatically manages BackButton based on router history,
falling back to BrowserRouter in regular browser environment.
Major changes:
- Redesign cabinet with Linear-style components and top navigation
- Replace detail modals with dedicated pages (users, broadcasts, email templates)
- Add email channel support for broadcasts
- Remove pull-to-refresh, improve drag-and-drop on touch devices
- Fix Telegram Mini App: fullscreen, back button, scroll restoration
- Unify admin pages color scheme to design system
- Mobile-first improvements: horizontal tabs for settings, better touch targets