В задании будем делать свою версию проекта 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)