Модель Telegram Mini App
Telegram Mini App — веб-интерфейс, открытый в окружении клиента Telegram. HTML, CSS и JavaScript по-прежнему создают страницу, но хост добавляет тему, кнопки и контекст запуска. Для нашего учебного приложения нужен небольшой сценарий: читатель выбирает курс и сохраняет личный план занятий. Перед первым экраном разделим ответственность браузера, Telegram и собственного сервера. Результат урока — модель приложения, которая не принимает внешне удобные данные за подтверждённые полномочия.
Назовём проект «Учебный план». Он использует четыре знакомых направления: JavaScript — двадцать уроков, HTML и CSS — шестнадцать, Markdown — двенадцать, производительность — двенадцать. Это учебный набор, а не подключение к личным данным настоящих читателей ProfessorWeb. Полный сервис с ботом, хранилищем и выпуском здесь не создаётся. Серия подготовит читающую оболочку, отдельные модули интерфейса, функцию проверки запуска и точный серверный контракт.
Три участника одного действия
Веб-интерфейс показывает каталог, держит несохранённый выбор и формирует намерение пользователя. Telegram открывает страницу и предоставляет функции окружения через Telegram.WebApp. Наш сервер хранит личный план и решает, кто может его читать или менять. Между этими участниками нет автоматического общего доверия. Даже если страница запущена внутри Telegram, человек может изменить отправляемый запрос, а обычный браузер — открыть тот же HTTPS-адрес без полноценного контекста.
Рассмотрим сохранение JavaScript. Интерфейс отправляет идентификатор javascript и выбранную частоту занятий. Сервер определяет владельца из подтверждённой сессии, а не из поля userId, написанного клиентом. Telegram не выполняет SQL нашей базы и не проверяет предметное правило «не больше семи занятий в неделю». Хост помогает передать контекст запуска, но приложение остаётся ответственным за аутентификацию, права доступа и допустимость изменения своего ресурса.
Если пользователь меняет имя в профиле Telegram, его план не должен становиться чужим. Имя и username — представление, идентификатор Telegram — устойчивый ключ для связывания аккаунта в нашей модели. При этом серверный ID собственной сущности плана тоже отдельный. Не подменяйте все три значения одной строкой «пользователь». В дальнейшем будем различать telegramUserId, session и plan version, потому что они отвечают разным вопросам: кто пришёл, чем подтверждён запрос и какое состояние меняется.
Каталог и личные данные
Каталог состоит из четырёх публичных объектов с короткими ключами. Их названия и количество уроков можно показать до входа, если проект так решил. Личный план включает courseId, sessionsPerWeek и version. Его owner не приходит от интерфейса как разрешение. Сервер назначает владельца по сессии и хранит связь самостоятельно. Сохранение плана не создаёт оплату, запись в учебную группу или подтверждённый прогресс прохождения всех уроков.
Зафиксируем полный предметный договор первого шага:
| Сущность | Пример | Источник истины |
|---|---|---|
| Курс | javascript, 20 уроков | публичный каталог проекта |
| Несохранённый выбор | javascript, 3 занятия | текущий интерфейс |
| Подтверждённый запуск | ID Telegram и время | серверная проверка initData |
| Сессия | ограниченный непрозрачный токен | сервер сессий |
| Сохранённый план | курс, частота, версия | собственный сервер и хранилище |
Одинаковое число в браузере и на сервере не означает одинаковую ответственность. Клиент может быстро подсказать ошибку, но сервер повторно проверяет диапазон. Временный выбор исчезает при закрытии, если приложение не сохраняет его отдельно. Подтверждение сохранения выдаётся только после авторитетного ответа сервера. Именно поэтому первая читательная оболочка не станет показывать «план сохранён», когда она всего лишь изменила текст на экране.
Способ открытия
Telegram поддерживает несколько вариантов запуска; у них различаются доступный контекст и способы передачи результата. Для нашей модели выбираем основное мини-приложение бота и прямую ссылку startapp. Официальное руководство перечисляет режимы. Мы не предполагаем, что sendData одинаково работает при любом запуске: он связан с клавиатурным сценарием, который не является основой этого курса.
Выбранный способ требует собственного бота и HTTPS-страницы, настроенных позднее отдельно. В этой задаче бот не создаётся, token не получается и страница не регистрируется в BotFather. Для обычного браузера предусмотрим понятный ограниченный режим: можно увидеть каталог и подготовить выбор, но нельзя выдать его за сохранённый личный план. Это полезно для разработки и отказа хоста, а не обход авторизации. Реальную серверную операцию отсутствие Telegram-контекста не разрешает.
URL открытия также не должен содержать секрет. Короткий startapp=course_javascript подходит для выбора публичного направления. Bot token, личный адрес, токен сессии или готовый запрос к чужому плану в него не помещаются. Ссылку может переслать другой человек, сохранить история и обработать внешний сервис. Поэтому контекст открытия формирует начальное намерение, а право доступа проверяется отдельно. Подпись запуска позднее подтвердит целостность набора, но не отменит предметные ограничения.
Состояния интерфейса
Определим несколько понятных состояний: каталог открыт, курс выбран, запуск подтверждается, план загружается, есть несохранённые изменения, сохранение выполняется, сохранение подтверждено, требуется повтор. Они нужны для кнопки и текста обратной связи. Без такого разделения один флаг «готово» начинает означать одновременно доступность SDK, наличие пользователя и завершённую запись данных. В результате приложение показывает успешный результат слишком рано или отправляет несколько одинаковых операций при быстрых нажатиях.
Ошибочный случай первого урока — сервер получает {userId:123, courseId:javascript} и верит числу 123 только потому, что клиент был открыт через бота. Другой человек может отправить тот же запрос. Правильная граница начинается с проверки сырого initData, затем собственной сессии и владельца ресурса. Наличие аватара на экране не участвует в этом решении. Модель исключает такой путь ещё до написания удобного интерфейса.
Ожидаемый результат — таблица сущностей, выбранный режим открытия и понятный договор о несохранённом выборе. Технические исполнения остаются not_run: нет настоящего хоста, сервера сессий или данных пользователей. Следующий урок создаст полный первый экран каталога, где выбор уже виден, но сохранять личный план приложение пока честно не обещает.