Тестирование, деплой, мониторинг и безопасность
Вы уже собрали основу мультиплеера:
Phaser 3 рендерит и управляет сценами
Playroom SDK соединяет игроков, даёт события join/quit и синхронизацию state
в Discord Activity вы учитываете встраивание, ресайз и особенности UX
у вас есть лобби, детерминированная идентичность сессии и старт матчаСледующий шаг — довести проект до уровня, который можно стабильно тестировать, безопасно публиковать и поддерживать после релиза.
!Общая картина: как изменения в коде превращаются в релиз и как вы ловите проблемы в продакшене
Что мы считаем хорошим результатом
К концу этой статьи у вас должно быть:
воспроизводимый процесс сборки vite build и локального предпросмотра vite preview
базовый набор тестов, которые ловят поломки до деплоя
понятный способ тестировать игру внутри Discord Activity
продакшен-деплой на HTTPS с учётом требований встраивания (iframe)
мониторинг ошибок и минимальная телеметрия
практики безопасности для клиентской мультиплеерной игрыТестирование: что реально нужно для мультиплеерной веб-игры
Тестирование в таких проектах полезно разделять по уровням. Важно не пытаться покрыть тестами всё, а закрыть самые дорогие ошибки: регрессии в логике сессии, лобби и синхронизации.
Виды тестов и где они применяются
| Уровень | Что проверяет | Что выгоднее тестировать в этом курсе | Инструменты |
|---|---|---|---|
| Юнит-тесты | чистые функции и маленькие модули | генерация lobbyKey, парсинг query, квантование, правила host election | Vitest |
| Интеграционные | несколько модулей вместе | запуск boot-flow: Discord init (как optional), выбор session key, вызов insertCoin | Vitest + моки |
| E2E | поведение в браузере | загрузка страницы, наличие canvas, базовая навигация сцен, работа ?room= | Playwright |
| Ручные мультиклиентные | синхронизация между вкладками/браузерами | join/quit, ready, старт матча, движение и сглаживание | 2–4 клиента + разные сети |
Термин E2E означает end-to-end: тест проходит путь как реальный пользователь в браузере.
Юнит-тест: пример для session key
Ваш lobbyKey — ключевой элемент матчмейкинга: он отвечает за то, что люди окажутся в одной сессии.
Если вы используете модуль вроде src/runtime/sessionKey.js, тестируйте:
при наличии instance_id возвращается discord:...
при наличии room возвращается web:...
при отсутствии обоих создаётся и сохраняется значение в sessionStorageПрактически важно: логика должна быть детерминированной и обратимо проверяемой.
E2E-тесты: минимум, который окупается
В E2E для этой игры обычно достаточно проверить:
страница открывается и не падает
Phaser canvas появился
лобби сцена отрисовалась и показала session keyЕсли вы делаете ?room=TEST, E2E тест должен убедиться, что session key отображает именно web:TEST.
Ручное мультиклиентное тестирование: обязательный чеклист
Это то, что автоматизировать сложно, но можно сделать системным процессом.
Откройте игру в двух разных профилях браузера (или один обычный режим + инкогнито)
Убедитесь, что оба клиента попали в одинаковую сессию (сверьте session key в лобби)
Проверьте join/quit
Проверьте ready у обоих игроков
Проверьте детерминированный старт матча (один становится host по правилу)
Проверьте движение и сглаживание удалённых игроков
Проверьте поведение при потере фокуса (клик по canvas возвращает управление)Отдельно полезно проверить разные условия сети:
один клиент через мобильный интернет
один клиент через VPN
один клиент нагружен (в фоне включён тяжёлый процесс)Тестирование Discord Activity: практический подход
Discord Activity часто ломается не в логике игры, а в окружении:
игра запускается в embedded-контейнере
нужен публичный HTTPS URL
важны заголовки, влияющие на iframeЛокальная разработка через публичный HTTPS
Во время разработки используйте туннель:
ngrok
Cloudflare TunnelСмысл: Discord должен открыть ваш dev-сервер по HTTPS.
Минимальный сценарий проверки в Discord
Откройте Activity
Проверьте, что UI не «уползает» при ресайзе
Проверьте ввод с клавиатуры после переключения окон Discord
Проверьте, что все участники одной Activity instance получают один session keyЕсли вы опираетесь на instance_id из query, вы должны показать его в дебаге (локально, без синхронизации), чтобы быстро видеть причину фрагментации сессий.
Деплой: сборка, хостинг, встраивание и стабильные URL
Сборка и предпросмотр
В Vite стандартный цикл такой:
build создаёт оптимизированные файлы
preview запускает локальный сервер, максимально похожий на продакшен-раздачуВажно: Discord Activity ближе по поведению к preview, чем к dev.
Базовые требования к хостингу для Discord Activity
Хостинг должен обеспечивать:
HTTPS
корректную раздачу статических файлов
возможность встраивания в iframe (Discord)Главный риск: заголовки безопасности.
X-Frame-Options: DENY сломает встраивание
Content-Security-Policy с frame-ancestors 'none' тоже сломаетПри этом полностью отключать защиту нельзя — нужно настроить её корректно.
Практика: разрешить встраивание только там, где нужно
Идея: разрешить iframe-встраивание для Discord, но не открывать игру для встраивания кем угодно.
Это делается через Content-Security-Policy директиву frame-ancestors.
Значения и точные домены зависят от текущей схемы Discord, поэтому правило такое:
не ставьте X-Frame-Options: DENY
используйте Content-Security-Policy и разрешайте нужные источники
проверяйте в DevTools, какие origin реально используются внутри ActivityОфициальная справка по CSP:
MDN: Content-Security-Policy
MDN: frame-ancestorsВыбор хостинга
Для статической Vite-сборки обычно подходят:
Cloudflare Pages
Vercel
NetlifyКритерии выбора:
можно ли управлять headers (CSP)
удобство превью деплоев (preview URLs)
простота HTTPS и кастомного доменаВерсионирование и обратная совместимость
У мультиплеера есть особенность: игроки могут оказаться на разных версиях клиента.
Минимальные правила:
держите деплой атомарным (одна версия статики для всех)
добавляйте в state модель версионность, если меняете ключиНапример, если вы переименовали ready или изменили формат данных:
вводите новый ключ рядом со старым на время миграции
удаляете старый только после того, как убедились, что все клиенты обновленыCI: автоматическая проверка перед деплоем
Даже простой CI пайплайн резко снижает количество сломанных релизов.
Минимальный пайплайн:
установка зависимостей
линт (если есть)
тесты
сборкаGitHub Actions документация:
GitHub ActionsПример workflow (адаптируйте под ваши команды):
Важно: секреты (токены, приватные ключи) добавляются через Secrets в репозитории, а не в код.
Мониторинг: как понять, что у игроков всё плохо
Когда игра в продакшене, ваша главная проблема — невидимые ошибки: у пользователя белый экран, а вы об этом не знаете.
Что мониторить в первую очередь
JS-ошибки и unhandled promise rejections
ошибки загрузки ассетов
время до появления лобби (условно: «время до первого полезного экрана»)
частоту переподключений и неожиданных выходовSentry как базовый мониторинг ошибок
Sentry удобен тем, что даёт:
сбор ошибок из браузера
группировку по типам
релизы и сопоставление с версиейСсылки:
Sentry for JavaScriptПравило безопасности:
DSN в клиенте не является секретом
но любые токены доступа и приватные ключи в клиенте хранить нельзяЛоги в мультиплеере: что логировать, чтобы не утонуть
Логирование должно быть событийным, а не «каждый кадр».
Хорошие события:
вход в лобби: session key, режим (web/discord)
join/quit: player id, текущее число игроков
старт матча: кто host, сколько ready
ошибки сети: таймауты, невозможность подключитьсяПлохие логи:
позиция игрока каждый тик
полный state дамп на каждом обновленииДашборд здоровья релиза
Минимальный набор метрик, который можно собирать даже без сложной системы:
количество загрузок
процент сессий, где лобби отрисовалось
количество ошибок на 100 сессий
средняя длительность сессииВначале это может быть просто Sentry + пара кастомных событий.
Безопасность: реалистичные правила для клиентской мультиплеерной игры
Важное ограничение: ваш клиент — недоверенная среда. Любой игрок может:
открыть DevTools
менять значения state
подменять запросыПоэтому задача безопасности в таком проекте часто формулируется так:
защитить пользователей и инфраструктуру
снизить влияние читов на игровой опыт
не утечь секретамиПеременные окружения и секреты
То, что начинается с VITE_, попадает в клиентский бандл.
Значит:
VITE_PLAYROOM_GAME_ID допустим
VITE_DISCORD_CLIENT_ID допустим
любые секреты недопустимыСекреты — это то, что даёт права:
access token
client secret
приватные ключиЕсли вам нужен обмен OAuth code на access token, это делается на сервере.
Защита от XSS и аккуратная работа с вводом
XSS — это внедрение вредоносного скрипта через пользовательские данные.
Риски в вашем проекте:
отображение name игрока
отображение любых текстовых полей из синхронизированного stateПрактическое правило:
в Phaser используйте this.add.text(...) как текст, не интерпретируйте строки как код
не вставляйте пользовательский ввод в DOM через небезопасные способыСправка:
MDN: Cross-site scripting (XSS)Политика безопасности контента (CSP)
CSP помогает уменьшить ущерб от XSS и нежелательных загрузок.
Что обычно фиксируют в CSP:
откуда можно грузить скрипты
откуда можно грузить картинки/аудио
кто может встраивать страницу (через frame-ancestors)Справка:
MDN: CSPВажно: CSP нужно тестировать в Discord, потому что слишком строгая политика может сломать embedded-запуск.
Зависимости и supply chain
Мультиплеерная игра на фронтенде почти всегда тянет много зависимостей.
Минимальные практики:
фиксируйте lockfile (package-lock.json или pnpm-lock.yaml)
периодически запускайте npm audit
обновляйте зависимости осознанно, особенно SDKСправка:
npm auditЗащита от спама state и «самострела» производительности
Даже без злонамеренности игра может сама себе навредить.
У вас уже есть хорошая практика:
фиксированный publish rate (например, 12 Гц)
квантованиеДополнительно полезно:
ограничить частоту смены emoji
не синхронизировать большие данные
вводить простые локальные лимиты: если игрок шлёт слишком часто, вы игнорируете часть действий на клиентеЭто не «настоящая» серверная защита, но это снижает нагрузку и делает поведение предсказуемым.
Античит как дизайн, а не как магия
Без авторитарного сервера вы не сможете полностью предотвратить читерство. Но можно уменьшить вред:
не делайте соревновательный рейтинг без серверной валидации
делайте правила, устойчивые к расхождениям (например, кооператив или party-game)
старайтесь, чтобы важные решения были проверяемыми и детерминированными (как host election)Финальный релиз-чеклист
Перед тем как отправлять Activity людям:
vite build и vite preview работают локально
по публичному HTTPS URL игра грузится без ошибок в консоли
внутри Discord Activity игра не белеет, ресайзится и принимает ввод
session key одинаковый у всех участников одной Activity instance
лобби: join/quit, ready, старт матча работают стабильно
мониторинг ошибок включён и вы видите тестовую ошибку в панели
нет секретов в VITE_ переменных и репозиторииЕсли этот чеклист выполнен, проект можно считать пригодным для реальных плейтестов, а не только для локальной демонстрации.