Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Кудашов Богдан #24

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

Поставь зависимости и запусти сервер. Для этого перейди в директорию задачи и выполни команду `npm install`. После установки зависимостей, выполни команду `npm run dev`. После запуска, перейди по адресу [localhost:5000](http://localhost:5000)

1. Сейчас цвета палитры захардкожены в файле `/client/picker.mjs`, cделай так, чтобы цвета запрашивались с сервера в функции `drawPalette`
--1. Сейчас цвета палитры захардкожены в файле `/client/picker.mjs`, cделай так, чтобы цвета запрашивались с сервера в функции `drawPalette`

2. На сервере мы будем использовать библиотеку [ws](https://github.com/websockets/ws) для подключения по протоколу [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). Она уже подключена, `WebSocket`-сервер уже правильно интегрирован с `express`, запущен и ждёт подключений по тому же порту, что и `express`.
--2. На сервере мы будем использовать библиотеку [ws](https://github.com/websockets/ws) для подключения по протоколу [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). Она уже подключена, `WebSocket`-сервер уже правильно интегрирован с `express`, запущен и ждёт подключений по тому же порту, что и `express`.

Сделай так, чтобы `WebSocket`-сервер начал обрабатывать подключения, т.е. события `connection`, как [в примере](https://github.com/websockets/ws#simple-server)

3. Сделай так, чтобы после подключения, новому клиенту присылался текущий массив состояния поля. Так как от сервера к клиенту будут передаваться разные сообщения, то удобно, чтобы каждое сообщение имело следующую структуру:
--3. Сделай так, чтобы после подключения, новому клиенту присылался текущий массив состояния поля. Так как от сервера к клиенту будут передаваться разные сообщения, то удобно, чтобы каждое сообщение имело следующую структуру:

```javascript
{
Expand All @@ -28,15 +28,15 @@

Как принимать и отправлять строковые сообщения уже было показано [в примере](https://github.com/websockets/ws#simple-server)

4. Сделай так, чтобы на клиенте при получении сообщения из `WebSocket` с начальным состоянием поля, заполнялось поле. Для этого измени обработчик события на `ws` в файле `/client/index.mjs`. В него сейчас приходят события [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent). Для десериализации данных в событии используй `JSON.parse()`. Для отрисовки начального состояния используй `drawer.putArray()`
--4. Сделай так, чтобы на клиенте при получении сообщения из `WebSocket` с начальным состоянием поля, заполнялось поле. Для этого измени обработчик события на `ws` в файле `/client/index.mjs`. В него сейчас приходят события [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent). Для десериализации данных в событии используй `JSON.parse()`. Для отрисовки начального состояния используй `drawer.putArray()`

5. Сделай так, чтобы при клике на поле, на сервер по `WebSocket` передавалось сообщение с координатами и цветом. Посылай сообщения с помощью метода [send()](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send). Для удобства, используй тот же формат, что и в сообщениях, посылаемых с сервера
--5. Сделай так, чтобы при клике на поле, на сервер по `WebSocket` передавалось сообщение с координатами и цветом. Посылай сообщения с помощью метода [send()](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send). Для удобства, используй тот же формат, что и в сообщениях, посылаемых с сервера

6. Сделай так, чтобы `WebSocket`-сервер при получении сообщения с координатами и цветом валидировал его на правильность координат и цвета, а затем рассылал всем активным клиентам. Как сделать `broadcast` посмотри [в примере](https://github.com/websockets/ws#server-broadcast)
--6. Сделай так, чтобы `WebSocket`-сервер при получении сообщения с координатами и цветом валидировал его на правильность координат и цвета, а затем рассылал всем активным клиентам. Как сделать `broadcast` посмотри [в примере](https://github.com/websockets/ws#server-broadcast)

7. Сделай так, чтобы новые пиксели не только рассылались остальным клиентам, но и добавлялись в поле, хранимое на стороне сервера. Это нужно, чтобы новые клиенты получали текущее изображение, а не начальное. Как сделаешь — проверь: открой приложение с одной вкладки браузера, нарисуй что-нибудь, затем открой со второй вкладки и убедись, что изображения совпадают
--7. Сделай так, чтобы новые пиксели не только рассылались остальным клиентам, но и добавлялись в поле, хранимое на стороне сервера. Это нужно, чтобы новые клиенты получали текущее изображение, а не начальное. Как сделаешь — проверь: открой приложение с одной вкладки браузера, нарисуй что-нибудь, затем открой со второй вкладки и убедись, что изображения совпадают

8. Удали из обработчика клика на поле отрисовку пикселя. Для этого сделай так, чтобы при `broadcast`-е текущему клиенту тоже отправлялось сообщение. А затем сделай так, чтобы отрисовка пикселя происходила только при получении клиентом сообщения из `WebSocket`. После этого сервер будет полностью контролировать целостность поля для рисования
--8. Удали из обработчика клика на поле отрисовку пикселя. Для этого сделай так, чтобы при `broadcast`-е текущему клиенту тоже отправлялось сообщение. А затем сделай так, чтобы отрисовка пикселя происходила только при получении клиентом сообщения из `WebSocket`. После этого сервер будет полностью контролировать целостность поля для рисования

9. Опубликуй своё приложение на [Heroku](https://id.heroku.com/login). Для этого потребуются `heroku-cli`, и `git`, если чего-то нет, пройди [вот эти шаги](https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up).

Expand All @@ -52,13 +52,13 @@
- Чтобы запускать приложение локально можно использовать `heroku local web`
- Чтобы деплоить новую версию приложения можно повторять последние два шага

10. Сделай так, что `WebSocket` сервер принимал только те подключения, которые передали в параметрах правильный `apiKey`. Для этого измени обработчик события `upgrade`. Подробнее о нём можно прочитать [здесь](https://nodejs.org/api/http.html#http_event_upgrade).
--10. Сделай так, что `WebSocket` сервер принимал только те подключения, которые передали в параметрах правильный `apiKey`. Для этого измени обработчик события `upgrade`. Подробнее о нём можно прочитать [здесь](https://nodejs.org/api/http.html#http_event_upgrade).

В обработчике уже используется объект `URL`, благодаря которому можно получить значение `apiKey` из `query string`. Как это сделать посмотри [тут](https://nodejs.org/api/url.html#url_class_urlsearchparams). Для закрытия соединения, которое произошло с невалидным `apiKey` можно использовать `socket.destroy()`. Подробнее можно прочитать [здесь](https://nodejs.org/api/stream.html#stream_writable_destroy_error)

11. \* Сделай так, чтобы после успешного `upgrade` происходила связь `ws` клиента и его `apiKey`. Сделать это можно внутри в `handleUpgrade()`. Для этого используй [WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap), где ключами будут объекты `ws`, а значениями — связанные с ними `apiKey`. `WeakMap` не будет препятствовать сборщику мусора очищать `ws`-соединения, когда они будут закрыты
--11. \* Сделай так, чтобы после успешного `upgrade` происходила связь `ws` клиента и его `apiKey`. Сделать это можно внутри в `handleUpgrade()`. Для этого используй [WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap), где ключами будут объекты `ws`, а значениями — связанные с ними `apiKey`. `WeakMap` не будет препятствовать сборщику мусора очищать `ws`-соединения, когда они будут закрыты

12. \* Сделай так, чтобы у каждого `apiKey` был свой таймаут. При подключении нового `WebSocket` клиента, посылай ему сообщение с временем, когда он в следующий раз может нарисовать на поле. Текущее время можно получить с помощью `new Date()`. Для отправки сериализуй дату с помощью `.toISOString()`.
--12. \* Сделай так, чтобы у каждого `apiKey` был свой таймаут. При подключении нового `WebSocket` клиента, посылай ему сообщение с временем, когда он в следующий раз может нарисовать на поле. Текущее время можно получить с помощью `new Date()`. Для отправки сериализуй дату с помощью `.toISOString()`.

На клиенте при получении сообщения, обновляй таймер с помощью `timeout.next =`. Десериализуй полученную дату с помощью `new Date(isoDateString)`. На сервере, при получении сообщения с координатами проверяй таймаут `apiKey` `ws` клиента и не принимай сообщения, которые произошли до истечения этого таймаута.

Expand Down
46 changes: 35 additions & 11 deletions client/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,46 @@ import drawer from "./drawer.mjs";
import picker from "./picker.mjs";

document.querySelector("#start").addEventListener("submit", e => {
e.preventDefault();
main(new FormData(e.currentTarget).get("apiKey"));
document.querySelector(".container").classList.add("ready");
e.preventDefault();
main(new FormData(e.currentTarget).get("apiKey"));
document.querySelector(".container").classList.add("ready");
});

const onFieldRecieved = (message) => {
console.log(message);

const data = JSON.parse(message?.['data'])
console.log(data);

switch (data['type']) {
case "place":
drawer.putArray(data['payload']['place']);
break;
case "click":
drawer.put(data.payload.x, data.payload.y, data.payload.color);
break;
case "timeout":
timeout.next = new Date(data.payload);
break;
}
}

const main = apiKey => {
const ws = connect(apiKey);
ws.addEventListener("message", console.log);
const ws = connect(apiKey);
ws.addEventListener("message", onFieldRecieved);

timeout.next = new Date();
drawer.onClick = (x, y) => {
drawer.put(x, y, picker.color);
};
timeout.next = new Date();
drawer.onClick = (x, y) => {
// отправляем на сервер
ws.send(JSON.stringify({
type: "click",
payload: { x: x, y: y, color: picker.color }
}));
};
};

const connect = apiKey => {
const url = `${location.origin.replace(/^http/, "ws")}?apiKey=${apiKey}`;
return new WebSocket(url);
const url = `${location.origin.replace(/^http/, "ws")}?apiKey=${apiKey}`;
return new WebSocket(url);
};

7 changes: 6 additions & 1 deletion client/picker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ const setAttributes = (element, object) => {
};

const drawPalette = async () => {
const colors = hardcodedColors;
const response = await fetch("/api/getColors");

console.log(response);

const colors = await response.json();

pickedColor = colors[0];
const palette = document.querySelector("#palette");
const fragment = document.createDocumentFragment();
Expand Down
Loading