//CHAT // -------------УСЛОВИЕ------------- // Этапы. // Используя функцию 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 вы запоминаете для того, что бы отправить следующий запрос начиная с него(т.е.с 5 в примере выше). // Тогда сервер отдаст вам только сообщения новее последнего запроса. // Chat Server // Можно глянуть тут: сервер Обратите внимание на массив messages и на функции в ассоциативном массиве RPCFuncs. // А также на эти строки кода // Спойлер // чатик // Но вам все равно надо переписать это на async, await, Promise и DOM. // Где это делать? // Если у вас не чрезмерно свежий хром, то это можно делать даже на локалхосте и открывать файл из браузера с диска. // Сервер позволяет соединятся с собой с любого домена. // -------------РЕШЕНИЕ------------- // Stage 0 let lastMessageId = 0; // 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')) // } // } // }) // }; // Stage 1 // const sendMessage = async (nick, message) => { async function sendMessage(nick, message){ let res = await jsonPost("http://students.a-level.com.ua:10012", { func: 'addMessage', nick: nick, message: message }) console.log(res.nextMessageId); } send.onclick = () => { let nickInput = nick.value; let msgInput = msg.value; if (nickInput && msgInput) { sendAndCheck(nickInput, msgInput) } msg.value = ''; }; // Stage 2 async function getMessages(){ let res = await jsonPost("http://students.a-level.com.ua:10012", { func: 'getMessages', messageId: lastMessageId }) lastMessageId = res.nextMessageId; historyDraw(res.data); } //Отрисовывает данные function historyDraw(array) { let history = array.reverse().map(item => `
${dateTransform(item.timestamp)}
${item.nick}:  ${item.message}
`); chat.insertAdjacentHTML('afterbegin', history.join('')); }; //Приводит дату к нужному виду function dateTransform(date) { date = new Date(date); return ((date.getHours() < 10 ? ('0' + date.getHours() ) : date.getHours()) + ':' + (date.getMinutes() < 10 ? ('0' + date.getMinutes() ) : date.getMinutes()) + ' ' + (date.getDate() < 10 ? ('0' + (date.getDate())) : date.getDate() )+ '.' + ((date.getMonth() + 1) < 10 ? ('0' + (date.getMonth() + 1)) : (date.getMonth() + 1)) + '.' + date.getFullYear()); } // Stage 4 // setInterval(() => getMessages(lastMessageId), 5000); // Stage 5 async function sendAndCheck(nick, message) { sendMessage(nick, message); getMessages(); } const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms)); // async function checkLoop() { setInterval(() => getMessages(lastMessageId), 5000); } async function checkLoop() { while (true) { await delay(5000); getMessages(lastMessageId); } } checkLoop(); // Stage 6 async function jsonPost(url, data) { let response=await fetch(url, { method: 'POST', body: JSON.stringify(data) }) if (response.ok) { return await response.json(); } else { alert("Ошибка HTTP: " + response.status); } };