// Chat Homework // Этапы. // Используя функцию jsonPost на адрес http://students.a-level.com.ua:10012 напишите чат-клиент, который: // Stage 0 // Для поиграться скопируйте в консоль функцию jsonPost (или запустите её с этой страницы) и вызовите её с теми или иными // объектами в качестве второго параметра. см. RPC. // jsonPost('http://students.a-level.com.ua:10012', {func: 'addMessage', nick: 'Anon', message: 'Я не умею копипастить в консоль, зато умею жать красную кнопку.'}) // Если после объявления функции jsonPost запустить пример выше, то вы напишите в чат. // Stage 1 // Отправляет сообщения в чат. Для проверки отслеживайте приходящий с сервера nextMessageId, который должен увеличиваться. // Интерфейс: поле ввода ника, поле ввода сообщения, кнопка отправки. // Stage 2 // Читает все сообщения из чата и выводит их в отдельном контейнере. // Интерфейс: некий div-контейнер, в котором на каждое сообщение создается div (или иной блочный контейнерный тэг) // и в него помещается ник и сообщение. Также там есть timestamp который может показывать время отправки сообщения. // Stage 3 // Читает только новые сообщения из чата. Для этого надо после каждого получения запоминать nextMessageId и отправлять его // при следующем запросе новых сообщений. Изначально nextMessageId равен нулю, что бы вычитать всю историю сообщений с сервера. // Stage 4 // Делаем Stage 3 в setInterval для периодической проверки сообщений (раз в 2 -5 секунд). // Stage 5 // Напишите асинхронную функцию отправки, которая внутри себя будет делать два запроса: на отправку и на проверку, для того // что бы минимизировать задержку между отправкой сообщения пользователя и его появлением в окне чата. // Оформите отдельно функции отправки и проверки новых сообщений как асинхронные (async, возвращает Promise): // async function sendMessage(nick, message) отсылает сообщение. // async function getMessages() получает сообщения и отрисовывает их в DOM // async function sendAndCheck() использует две предыдущие для минимизации задержки между отправкой сообщения и приходом их. // Именно эта функция должна запускаться по кнопке. // async function checkLoop() использует delay и бесконечный цикл для периодического запуска getMessages(). // Stage 6 // Прогуглить и разобраться с fetch и заменить внутренности jsonPost на код, использующий fetch вместо XMLHttpRequest. // Информация // jsonPost // Данная промисифицированная функция (кстати, сделайте из неё асинхронную) умеет общаться с моим чат-сервером отправляя // RPC запросы используя JSON. Remote Procedure Call - вызов функций удаленно, имя функции и параметры передаются AJAX-ом // в формате (например) JSON. // function jsonPost(url, data) // { // return new Promise((resolve, reject) => { // var x = new XMLHttpRequest(); // x.onerror = () => reject(new Error('jsonPost failed')) // //x.setRequestHeader('Content-Type', 'application/json'); // x.open('POST', url, true); // x.send(JSON.stringify(data)) // x.onreadystatechange = () => { // if (x.readyState == XMLHttpRequest.DONE && x.status == 200){ // resolve(JSON.parse(x.responseText)) // } // else if (x.status != 200){ // reject(new Error('status is not 200')) // } // } // }) // } // Первым параметром указывается URL (см. выше) на который отправляется методом POST с JSON, вторым - готовый к JSONификации // объект, который вы хотите отправить на сервер. // RPC // addMessage // Если вы отправите подобный JSON на сервер (см jsonPost), то сервер запишет ваше сообщение во внутренний массив и отдаст // новую длину массива. После этого другие могут прочесть это сообщение, используя метод getMessages. // {func: 'addMessage', nick: 'msg', message: 'msg'} // Поле func является обязательным и содержит имя функции, которая должна быть вызвана на сервере. Функция на сервере получает // параметром остальной объект (поля nick и message) В ответ вы получите так же объект: // {nextMessageId: 100500} // где 100500: новая длина массива сообщений на сервере после добавления вашего сообщения. // getMessages // Позволяет прочесть часть массива сообщений от отпределенного индекса и до конца. // Используется для последовательной дочитки новых сообщений. Для этого надо передать в jsonPost подобный объект: // {func: 'getMessages', messageId: 0} // При значении 0 в messageId сервер отдаст сообщения от 0 до конца, т. е. весь массив: // { // 'data': [ // { // 'nick': 'test', // 'message': 'test', // 'timestamp': 1524225450317 // }, // { // 'nick': 'test', // 'message': 'test2', // 'timestamp': 1524225460973 // }, // { // 'nick': 'test', // 'message': 'test3', // 'timestamp': 1524225504849 // }, // { // 'nick': 'SirkoSobaka', // 'message': 'Hello!', // 'timestamp': 1524226323310 // }, // { // 'nick': 'SirkoSobaka', // 'message': 'Hello!', // 'timestamp': 1524226326628 // }, // ], // 'nextMessageId': 5 // } // После чего вы итерируете по data и выводите каждый отдельный объект как DOM-элемент с текстом, и, возможно, вложенной // версткой (время в отдельном элементе и т.п.) // nextMessageId вы запоминаете для того, что бы отправить следующий запрос начиная с него (т. е. с 68 в примере выше). // Тогда сервер отдаст вам только сообщения новее последнего запроса. // Chat Server // Если у вас не чрезмерно свежий хром, то это можно делать даже на локалхосте и открывать файл из браузера с диска. // Сервер позволяет соединятся с собой с любого домена. // Удачи :-) const url = 'http://students.a-level.com.ua:10012'; const nick = document.getElementById('nick'); const message = document.getElementById('message'); const btnSendMessage = document.getElementById('button'); const container = document.getElementById('container'); let nextMessageId = 1; let delay = 1000; setInterval(getMessages, delay); function getMessages() { jsonPost(url, { func: 'getMessages', messageId: nextMessageId }) .then((response) => response.json()) .then((data) => { nextMessageId = data.nextMessageId; updateMessages(data.data); }); } function jsonPost(url, data) { return fetch(url, { method: 'POST', body: JSON.stringify(data), }); } function updateMessages(messageList) { messageList.forEach((msg) => { newGotMessage(msg); }); } function newGotMessage({ nick, message, timestamp }) { const newBlock = document.createElement('div'); newBlock.classList.add('block'); const time = new Date(timestamp); let html = ''; html += `
${nick}
`; html += `
`; html += `${time.toString()}
`; newBlock.innerHTML = html; container.prepend(newBlock); } btnSendMessage.addEventListener('click', sendAndCheck); async function sendAndCheck() { await sendMessage(); getMessages(); message.value = ''; } async function sendMessage() { jsonPost(url, { func: 'addMessage', nick: nick.value, message: message.value, }); }