В задании будем делать свою версию проекта Place размером 256x256
Клиентский код лежит в папке /client
, серверный — в файле server.mjs
Поставь зависимости и запусти сервер. Для этого перейди в директорию задачи и выполни команду npm install
. После установки зависимостей, выполни команду npm run dev
. После запуска, перейди по адресу localhost:5000
-
Сейчас цвета палитры захардкожены в файле
/client/picker.mjs
, cделай так, чтобы цвета запрашивались с сервера в функцииdrawPalette
-
На сервере мы будем использовать библиотеку ws для подключения по протоколу WebSocket. Она уже подключена,
WebSocket
-сервер уже правильно интегрирован сexpress
, запущен и ждёт подключений по тому же порту, что иexpress
.
Сделай так, чтобы WebSocket
-сервер начал обрабатывать подключения, т.е. события connection
, как в примере
- Сделай так, чтобы после подключения, новому клиенту присылался текущий массив состояния поля. Так как от сервера к клиенту будут передаваться разные сообщения, то удобно, чтобы каждое сообщение имело следующую структуру:
{
type: "/* action */", // тип сообщения
payload: {
/* ... */ // контент сообщения
}
}
Через веб-сокеты можно отправлять и, соответственно, принимать данные в разных форматах.
Нам же подойдут обычные строки. Чтобы передать объект сообщения в виде строки, его надо предварительно сериализовать, например, с помощью JSON.stringify()
.
Как принимать и отправлять строковые сообщения уже было показано в примере
-
Сделай так, чтобы на клиенте при получении сообщения из
WebSocket
с начальным состоянием поля, заполнялось поле. Для этого измени обработчик события наws
в файле/client/index.mjs
. В него сейчас приходят события MessageEvent. Для десериализации данных в событии используйJSON.parse()
. Для отрисовки начального состояния используйdrawer.putArray()
-
Сделай так, чтобы при клике на поле, на сервер по
WebSocket
передавалось сообщение с координатами и цветом. Посылай сообщения с помощью метода send(). Для удобства, используй тот же формат, что и в сообщениях, посылаемых с сервера -
Сделай так, чтобы
WebSocket
-сервер при получении сообщения с координатами и цветом валидировал его на правильность координат и цвета, а затем рассылал всем активным клиентам. Как сделатьbroadcast
посмотри в примере -
Сделай так, чтобы новые пиксели не только рассылались остальным клиентам, но и добавлялись в поле, хранимое на стороне сервера. Это нужно, чтобы новые клиенты получали текущее изображение, а не начальное. Как сделаешь — проверь: открой приложение с одной вкладки браузера, нарисуй что-нибудь, затем открой со второй вкладки и убедись, что изображения совпадают
-
Удали из обработчика клика на поле отрисовку пикселя. Для этого сделай так, чтобы при
broadcast
-е текущему клиенту тоже отправлялось сообщение. А затем сделай так, чтобы отрисовка пикселя происходила только при получении клиентом сообщения изWebSocket
. После этого сервер будет полностью контролировать целостность поля для рисования -
Опубликуй своё приложение на Heroku. Для этого потребуются
heroku-cli
, иgit
, если чего-то нет, пройди вот эти шаги.
- Авторизируйся в Heroku:
heroku login
- Создай новое приложение в
Heroku
:heroku create
- Версия
nodejs
для нашего приложения уже указана в полеengines
вpackage.json
- Закомить все изменения:
git add .
,git commit -m "add my app"
- Задеплой в
Heroku
:git push heroku master
После деплоя:
- При деплое
Heroku
дляNode.js
-приложений по умолчанию запускаетnpm-script
с именемstart
- Чтобы запускать приложение локально можно использовать
heroku local web
- Чтобы деплоить новую версию приложения можно повторять последние два шага
- Сделай так, что
WebSocket
сервер принимал только те подключения, которые передали в параметрах правильныйapiKey
. Для этого измени обработчик событияupgrade
. Подробнее о нём можно прочитать здесь.
В обработчике уже используется объект URL
, благодаря которому можно получить значение apiKey
из query string
. Как это сделать посмотри тут. Для закрытия соединения, которое произошло с невалидным apiKey
можно использовать socket.destroy()
. Подробнее можно прочитать здесь
-
* Сделай так, чтобы после успешного
upgrade
происходила связьws
клиента и егоapiKey
. Сделать это можно внутри вhandleUpgrade()
. Для этого используй WeakMap, где ключами будут объектыws
, а значениями — связанные с нимиapiKey
.WeakMap
не будет препятствовать сборщику мусора очищатьws
-соединения, когда они будут закрыты -
* Сделай так, чтобы у каждого
apiKey
был свой таймаут. При подключении новогоWebSocket
клиента, посылай ему сообщение с временем, когда он в следующий раз может нарисовать на поле. Текущее время можно получить с помощьюnew Date()
. Для отправки сериализуй дату с помощью.toISOString()
.
На клиенте при получении сообщения, обновляй таймер с помощью timeout.next =
. Десериализуй полученную дату с помощью new Date(isoDateString)
. На сервере, при получении сообщения с координатами проверяй таймаут apiKey
ws
клиента и не принимай сообщения, которые произошли до истечения этого таймаута.
В ответ на принятые сообщения обновляй таймаут на 10s-60s
и отсылай его клиенту. В ответ на непринятые сообщения просто посылай таймаут.
Подсказка: чтобы получить дату через
n
секунд послеdate
можно использовать такое выражение:new Date(date.valueOf() + n * 1000)