Skip to main content

Onboarding

1. Summary

Goal: Гейтинг доступа к приложению через проверку подписок на Telegram бота и канал. Обеспечивает контролируемую воронку входа пользователей.

User Value: Быстрый и понятный старт с пошаговым гайдом. Пользователь чётко видит, что нужно сделать для получения доступа.


2. Business Logic

Activation Flow

Subscription Checks

Проверка: User.botStatus

Допустимые значения:

  • ACTIVE — пользователь написал /start боту
  • REACTIVATED — пользователь разблокировал бота после блокировки

Недопустимые:

  • NEW_USER — пользователь создан в TMA, но не писал боту
  • BLOCKED — пользователь заблокировал бота

Как обновляется: При получении Telegram webhook от бота.

Onboarding Steps Interface

interface OnboardingStep {
name: 'bot_subscription' | 'channel_subscription';
description: string; // Локализованное описание
completed: boolean; // Выполнен ли шаг
required: boolean; // Обязателен для активации (всегда true)
}

interface OnboardingStatus {
isComplete: boolean; // Все шаги выполнены
canActivate: boolean; // Можно завершить онбординг
nextStep: string | null; // Следующий необходимый шаг
steps: OnboardingStep[]; // Список всех шагов
}

Core Logic

getOnboardingStatus(telegramId, forceRefresh):

  1. Найти пользователя по telegramId
  2. Проверить botStatus (ACTIVE или REACTIVATED)
  3. Проверить подписку на канал через Telegram API
  4. Сформировать список шагов с их статусами
  5. Определить canActivate = allRequired.every(completed)

completeOnboarding(telegramId):

  1. Получить актуальный статус онбординга
  2. Проверить canActivate === true
  3. Если да — вернуть welcome message
  4. Если нет — вернуть ошибку с недостающими шагами
Важно

OnboardingService НЕ активирует InviteSession. Активация выполняется через ActivationRewardService в Telegram bot flow. Онбординг только проверяет подписки. См. Activation.

Protection

ДействиеRate LimitAuthValidation
Get statusnone (public)GetOnboardingStatusSchema
Completenone (public)CompleteOnboardingSchema
Refresh subscriptionsnone (public)RefreshSubscriptionsSchema
Почему нет auth?

Эндпоинты публичные, потому что пользователь ещё не аутентифицирован на этом этапе. Аутентификация происходит после успешного онбординга.

Edge Cases

СитуацияПоведениеUI
Прямой вход в TMA без invite linkВсё равно проверяем подпискиПоказываем шаги как обычно
Force refresh (?force=true)Bypass кэша, актуальная проверкаИспользуется кнопкой "Обновить"
Telegram API недоступенGraceful fallback, allow accessПропускаем пользователя
Bot status = BLOCKEDШаг bot_subscription = incomplete"Разблокируйте бота"
Канал не существуетОшибка при проверкеЛогируем, не блокируем
Бэкенд недоступен (сетевая ошибка)onboardingState = 'server_unavailable'ServerUnavailableScreen с retry
Бэкенд не отвечает (TCP timeout >15с)Timeout safety net → server_unavailableТот же ServerUnavailableScreen
Сессия Telegram устарелаonboardingState = 'session_expired'SessionExpiredScreen — перезапуск Mini App

Gate Screens (LoadingOrchestrator)

LoadingOrchestrator управляет инициализацией и может показать блокирующий экран вместо приложения:

ЭкранУсловиеКнопки
BrandedLoadingScreenИдёт инициализация— (fake progress bar)
ServerUnavailableScreenОба API-запроса (maintenance + season) упали с network error, либо timeout 15с"Повторить попытку", "Закрыть приложение"
SessionExpiredScreeninitData Telegram отсутствует или невалиден"Перезапустить приложение"
BannedScreenПользователь забанен"Закрыть приложение"
Bot/Channel activationПодписки не выполненыСсылки на бота/канал
ServerUnavailableScreen — детекция

Детекция backend unavailability происходит в initialize() через networkError: boolean флаг в return type обоих check-функций (checkMaintenanceStatus, checkSeasonStatus). Если хотя бы одна вернула networkError: true и при этом maintenance не активен — показывается ServerUnavailableScreen. Retry полностью сбрасывает state machine и запускает инициализацию заново.


3. ADR (Architectural Decisions)

Почему OnboardingService НЕ активирует сессии?

Проблема: Изначально OnboardingService и активировал подписки, и активировал InviteSession. Это нарушало Single Responsibility.

Решение: Разделение ответственности:

  • OnboardingService — только проверка подписок
  • ActivationRewardService — активация сессий и распределение наград

Альтернативы (отклонены):

  • Всё в одном сервисе — сложнее тестировать, нарушает SRP

Последствия:

  • Чистая архитектура
  • Проще unit-тесты
  • POST /onboarding/complete не имеет side effects кроме возврата сообщения

Почему проверки для ВСЕХ пользователей?

Проблема: Пользователи могут войти в TMA напрямую (без invite link), минуя создание PENDING InviteSession.

Решение: Проверять подписки для всех, независимо от наличия PENDING сессии.

Последствия:

  • Консистентный гейтинг для всех способов входа
  • Нет "обходного пути" через direct access

Почему публичные эндпоинты без auth?

Проблема: Как аутентифицировать пользователя, который ещё не прошёл онбординг?

Решение: Эндпоинты /onboarding/* публичные, используют только telegramId из query/body.

Риски и митигация:

  • Риск: Спам запросов → Митигация: Rate limit на уровне IP (implicit)
  • Риск: Утечка данных → Митигация: Эндпоинты возвращают только boolean статусы, никаких sensitive данных

Последствия:

  • Простой flow для нового пользователя
  • Нет chicken-and-egg проблемы с auth

4. Architecture

Service Dependencies

Key Components

КомпонентПутьОписание
OnboardingServicebackend/src/domains/users/services/onboarding.service.tsПроверка подписок
OnboardingControllerbackend/src/domains/users/controllers/onboarding.controller.tsHTTP handlers
TelegramSubscriptionServicebackend/src/domains/telegram/services/telegram-subscription.service.tsTelegram API интеграция
Routesbackend/src/domains/users/routes/onboarding.routes.tsAPI endpoints
Schemasbackend/src/domains/users/schemas/onboarding.schemas.tsRequest/Response validation

Method Details

OnboardingService Methods

getOnboardingStatus(telegramId: number, forceRefresh?: boolean)

  • Возвращает: OnboardingStatus
  • Кэширование: Нет (проверки реал-тайм)
  • Force refresh: Bypass any implicit caching

completeOnboarding(telegramId: number)

  • Возвращает: { wasActivated: boolean, message: string }
  • Ошибки: ONBOARDING_NOT_COMPLETE если canActivate = false

checkBotSubscription(telegramId: number)

  • Возвращает: boolean
  • Проверяет: user.botStatus in ['ACTIVE', 'REACTIVATED']

checkChannelSubscription(telegramId: number)

  • Возвращает: boolean
  • Использует: TelegramSubscriptionService

refreshSubscriptionStatus(telegramId: number)

  • Возвращает: { refreshed: boolean, subscriptions: SubscriptionStatus[] }
  • Для: Troubleshooting и testing

5. Database Schema

МодельОписаниеКлючевые поля
UserСодержит botStatusbotStatus (BotRegistrationState enum)
InviteSessionТрекинг перехода по ссылкеstate, telegramId, metadata, expiresAt

BotRegistrationState Enum

enum BotRegistrationState {
NEW_USER // Создан в TMA, никогда не писал боту
ACTIVE // Написал /start боту
REACTIVATED // Разблокировал бота после блокировки
BLOCKED // Заблокировал бота
}

InviteSession States

Relationships


6. API Endpoints

Public API

МетодЭндпоинтОписаниеDocs
GET/api/onboarding/statusПроверка статуса онбординга
POST/api/onboarding/completeЗавершение онбординга
POST/api/onboarding/refresh-subscriptionsПринудительное обновление статуса
Request/Response Examples

GET /api/onboarding/status?telegramId=123456789

// Response 200
{
"success": true,
"data": {
"isComplete": false,
"canActivate": false,
"nextStep": "channel_subscription",
"steps": [
{
"name": "bot_subscription",
"description": "Подпишитесь на бота",
"completed": true,
"required": true
},
{
"name": "channel_subscription",
"description": "Подпишитесь на канал",
"completed": false,
"required": true
}
]
}
}

POST /api/onboarding/complete

// Request Body
{
"telegramId": 123456789
}

// Response 200 (success)
{
"success": true,
"data": {
"wasActivated": true,
"message": "Добро пожаловать в GOLOOT!"
}
}

// Response 400 (not ready)
{
"success": false,
"error": "Onboarding not complete",
"data": {
"missingSteps": ["channel_subscription"]
}
}

POST /api/onboarding/refresh-subscriptions

// Request Body
{
"telegramId": 123456789
}

// Response 200
{
"success": true,
"data": {
"refreshed": true,
"subscriptions": [
{ "type": "bot", "status": true },
{ "type": "channel", "status": false }
]
}
}

Stub Endpoints (не реализованы)

МетодЭндпоинтОписаниеСтатус
GET/api/onboarding/notificationsПолучение уведомленийStub (empty array)
POST/api/onboarding/notifications/mark-readПометить прочитаннымиStub (always success)

7. Interactive Guide

Изменение от 29.01.2026

Добавлена система интерактивного тура для новых пользователей после успешной активации. Использует react-joyride для пошагового обучения.

Goal

После прохождения activation flow (подписки на бота/канал), новым пользователям показывается интерактивный тур по приложению. Цель — познакомить с основными возможностями за ~1 минуту.

Tour System Architecture

Components

КомпонентПутьОписание
OnboardingGuidefrontend/src/components/onboarding/OnboardingGuide.tsxReact-joyride wrapper, управление туром
TourPromptModalfrontend/src/components/onboarding/TourPromptModal.tsxМодалка "Хочешь пройти тур?"
ConfirmSkipModalfrontend/src/components/onboarding/ConfirmSkipModal.tsxПодтверждение пропуска тура
onboardingStorefrontend/src/stores/onboardingStore.tsZustand store для UI состояния
tourStepsfrontend/src/config/tourSteps.tsКонфигурация 12 шагов тура

Tour Steps (12 шагов)

Шаги нацелены на основные UI элементы через data-tour атрибуты:

#TargetОписание
1balance-scrapБаланс Scrap — игровая валюта
2balance-xpXP — опыт для прокачки уровня
3daily-caseЕжедневный бесплатный кейс
4quests-sectionКвесты — задания для наград
5quest-filtersФильтры квестов по типам
6nav-spinНавигация: Рулетка
7nav-casesНавигация: Кейсы
8nav-profileНавигация: Профиль
9inventory-buttonКнопка инвентаря (скины)
10steam-trade-urlSteam Trade URL для вывода
11settings-buttonКнопка настроек
12faq-buttonFAQ для помощи

User Flow

Новый пользователь:

  1. Проходит activation (подписки)
  2. После успешной активации → onboardingGuideCompleted = false (default)
  3. App.tsx показывает TourPromptModal
  4. Пользователь выбирает:
    • "Начать тур" → Joyride показывает 12 шагов
    • "Не сейчас" → ConfirmSkipModal → подтверждение → сохранение флага в БД

Повторный запуск:

  1. Settings → "Пройти тур заново"
  2. setOnboardingGuideCompleted(false)showPrompt()
  3. TourPromptModal появляется снова

Backend Integration

Поле: UserSettings.onboardingGuideCompleted (Boolean, default: false)

Тип синхронизации: Backend DB only (НЕ сохраняется в localStorage)

Обновление:

  • При завершении тура: PUT /api/users/settings { onboardingGuideCompleted: true }
  • При пропуске тура: то же самое
  • При "Пройти заново": локальный setOnboardingGuideCompleted(false) → модалка появляется
Важно

onboardingGuideCompleted НЕ сохраняется в localStorage (в отличие от soundEnabled). Это предотвращает bypass тура через очистку localStorage.

Edge Cases

СитуацияПоведение
Пользователь закрывает модалку (X)Тур не засчитывается, модалка появится при следующем запуске
Пользователь нажимает ESC во время тураJoyride останавливается, тур не засчитывается
Target элемент не найденJoyride пропускает шаг (event: TARGET_NOT_FOUND)
Пользователь переключил таб во время тураТур останавливается (элементы скрыты)

  • Activation — активация InviteSession и награды
  • Profile — профиль после активации
  • Settings — настройки пользователя (включая onboardingGuideCompleted)
  • Referrals — реферальная система
  • Telegram Bot — интеграция с Telegram
  • UTM Tracking — трекинг источников при регистрации
  • Security Matrix — обзор защит