chat.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. //CHAT
  2. // -------------УСЛОВИЕ-------------
  3. // Этапы.
  4. // Используя функцию jsonPost на адрес http://students.a-level.com.ua:10012 напишите чат-клиент, который:
  5. // Stage 0
  6. // Для поиграться скопируйте в консоль функцию jsonPost(или запустите её с этой страницы) и
  7. // вызовите её с теми или иными объектами в качестве второго параметра.см.RPC.
  8. // jsonPost("http://students.a-level.com.ua:10012", {
  9. // func: 'addMessage', nick: "Anon",
  10. // message: 'Я не умею копипастить в консоль, зато умею жать красную кнопку.'
  11. // })
  12. // Если после объявления функции jsonPost запустить пример выше, то вы напишите в чат.
  13. // Stage 1
  14. // Отправляет сообщения в чат.Для проверки отслеживайте приходящий с сервера nextMessageId,
  15. // который должен увеличиваться. Интерфейс: поле ввода ника, поле ввода сообщения, кнопка отправки.
  16. // Stage 2
  17. // Читает все сообщения из чата и выводит их в отдельном контейнере.Интерфейс: некий div - контейнер,
  18. // в котором на каждое сообщение создается div(или иной блочный контейнерный тэг) и в него помещается ник и сообщение.
  19. // Также там есть timestamp который может показывать время отправки сообщения.
  20. // Stage 3
  21. // Читает только новые сообщения из чата.Для этого надо после каждого получения запоминать nextMessageId
  22. // и отправлять его при следующем запросе новых сообщений.Изначально nextMessageId равен нулю,
  23. // чтобы вычитать всю историю сообщений с сервера.
  24. // Stage 4
  25. // Делаем Stage 3 в setInterval для периодической проверки сообщений (раз в 2 -5 секунд).
  26. // Stage 5
  27. // Напишите асинхронную функцию отправки, которая внутри себя будет делать два запроса: на отправку и на проверку,
  28. // для того чтобы минимизировать задержку между отправкой сообщения пользователя и его появлением в окне чата.
  29. // Оформите отдельно функции отправки и проверки новых сообщений как асинхронные(async, возвращает Promise):
  30. // async function sendMessage(nick, message) отсылает сообщение.
  31. // async function getMessages() получает сообщения и отрисовывает их в DOM
  32. // async function sendAndCheck() использует две предыдущие для минимизации задержки между отправкой сообщения и приходом их.
  33. // Именно эта функция должна запускаться по кнопке.
  34. // async function checkLoop() использует delay и бесконечный цикл для периодического запуска getMessages().
  35. // Stage 6
  36. // Прогуглить и разобраться с fetch и заменить внутренности jsonPost на код, использующий fetch вместо XMLHttpRequest.
  37. // Информация
  38. // jsonPost
  39. // Данная промисифицированная функция(кстати, сделайте из неё асинхронную) умеет общаться с моим чат - сервером
  40. // отправляя RPC запросы используя JSON.Remote Procedure Call - вызов функций удаленно, имя функции
  41. // и параметры передаются AJAX - ом в формате(например) JSON.
  42. // function jsonPost(url, data)
  43. // {
  44. // return new Promise((resolve, reject) => {
  45. // var x = new XMLHttpRequest();
  46. // x.onerror = () => reject(new Error('jsonPost failed'))
  47. // //x.setRequestHeader('Content-Type', 'application/json');
  48. // x.open("POST", url, true);
  49. // x.send(JSON.stringify(data))
  50. // x.onreadystatechange = () => {
  51. // if (x.readyState == XMLHttpRequest.DONE && x.status == 200){
  52. // resolve(JSON.parse(x.responseText))
  53. // }
  54. // else if (x.status != 200){
  55. // reject(new Error('status is not 200'))
  56. // }
  57. // }
  58. // })
  59. // }
  60. // Первым параметром указывается URL(см.выше) на который отправляется методом POST с JSON, вторым - готовый к JSONификации объект,
  61. // который вы хотите отправить на сервер.
  62. // RPC
  63. // addMessage
  64. // Если вы отправите подобный JSON на сервер(см jsonPost), то сервер запишет ваше сообщение во внутренний массив
  65. // и отдаст новую длину массива.После этого другие могут прочесть это сообщение, используя метод getMessages.
  66. // {func: "addMessage", nick: 'msg', message: 'msg'}
  67. // Поле func является обязательным и содержит имя функции, которая должна быть вызвана на сервере.
  68. // Функция на сервере получает параметром остальной объект(поля nick и message) В ответ вы получите так же объект:
  69. // {nextMessageId: 100500}
  70. // где 100500: новая длина массива сообщений на сервере после добавления вашего сообщения.
  71. // getMessages
  72. // Позволяет прочесть часть массива сообщений от отпределенного индекса и до конца.
  73. // Используется для последовательной дочитки новых сообщений.Для этого надо передать в jsonPost подобный объект:
  74. // {func: "getMessages", messageId: 0}
  75. // При значении 0 в messageId сервер отдаст сообщения от 0 до конца, т. е. весь массив:
  76. // {
  77. // "data": [
  78. // {
  79. // "nick": "test",
  80. // "message": "test",
  81. // "timestamp": 1524225450317
  82. // },
  83. // {
  84. // "nick": "test",
  85. // "message": "test2",
  86. // "timestamp": 1524225460973
  87. // },
  88. // {
  89. // "nick": "test",
  90. // "message": "test3",
  91. // "timestamp": 1524225504849
  92. // },
  93. // {
  94. // "nick": "SirkoSobaka",
  95. // "message": "Hello!",
  96. // "timestamp": 1524226323310
  97. // },
  98. // {
  99. // "nick": "SirkoSobaka",
  100. // "message": "Hello!",
  101. // "timestamp": 1524226326628
  102. // },
  103. // ],
  104. // "nextMessageId": 5
  105. // }
  106. // После чего вы итерируете по data и выводите каждый отдельный объект как DOM - элемент с текстом, и, возможно,
  107. // вложенной версткой(время в отдельном элементе и т.п.)
  108. // nextMessageId вы запоминаете для того, что бы отправить следующий запрос начиная с него(т.е.с 5 в примере выше).
  109. // Тогда сервер отдаст вам только сообщения новее последнего запроса.
  110. // Chat Server
  111. // Можно глянуть тут: сервер Обратите внимание на массив messages и на функции в ассоциативном массиве RPCFuncs.
  112. // А также на эти строки кода
  113. // Спойлер
  114. // чатик
  115. // Но вам все равно надо переписать это на async, await, Promise и DOM.
  116. // Где это делать?
  117. // Если у вас не чрезмерно свежий хром, то это можно делать даже на локалхосте и открывать файл из браузера с диска.
  118. // Сервер позволяет соединятся с собой с любого домена.
  119. // -------------РЕШЕНИЕ-------------
  120. // Stage 0
  121. let lastMessageId = 0;
  122. // function jsonPost(url, data)
  123. // {
  124. // return new Promise((resolve, reject) => {
  125. // var x = new XMLHttpRequest();
  126. // x.onerror = () => reject(new Error('jsonPost failed'))
  127. // //x.setRequestHeader('Content-Type', 'application/json');
  128. // x.open("POST", url, true);
  129. // x.send(JSON.stringify(data))
  130. // x.onreadystatechange = () => {
  131. // if (x.readyState == XMLHttpRequest.DONE && x.status == 200){
  132. // resolve(JSON.parse(x.responseText))
  133. // }
  134. // else if (x.status != 200){
  135. // reject(new Error('status is not 200'))
  136. // }
  137. // }
  138. // })
  139. // };
  140. // Stage 1
  141. // const sendMessage = async (nick, message) => {
  142. async function sendMessage(nick, message){
  143. let res = await jsonPost("http://students.a-level.com.ua:10012", {
  144. func: 'addMessage', nick: nick,
  145. message: message
  146. })
  147. console.log(res.nextMessageId);
  148. }
  149. send.onclick = () => {
  150. let nickInput = nick.value;
  151. let msgInput = msg.value;
  152. if (nickInput && msgInput) { sendAndCheck(nickInput, msgInput) }
  153. msg.value = '';
  154. };
  155. // Stage 2
  156. async function getMessages(){
  157. let res = await jsonPost("http://students.a-level.com.ua:10012", {
  158. func: 'getMessages', messageId: lastMessageId
  159. })
  160. lastMessageId = res.nextMessageId;
  161. historyDraw(res.data);
  162. }
  163. //Отрисовывает данные
  164. function historyDraw(array) {
  165. let history = array.reverse().map(item => `<div class='postWrapper'><div class='timeWrapper'>${dateTransform(item.timestamp)}</div> <b>${item.nick}:</b> &nbsp${item.message}</div>`);
  166. chat.insertAdjacentHTML('afterbegin', history.join(''));
  167. };
  168. //Приводит дату к нужному виду
  169. function dateTransform(date) {
  170. date = new Date(date);
  171. 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());
  172. }
  173. // Stage 4
  174. // setInterval(() => getMessages(lastMessageId), 5000);
  175. // Stage 5
  176. async function sendAndCheck(nick, message) {
  177. sendMessage(nick, message);
  178. getMessages();
  179. }
  180. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms));
  181. // async function checkLoop() { setInterval(() => getMessages(lastMessageId), 5000); }
  182. async function checkLoop() {
  183. while (true) {
  184. await delay(5000);
  185. getMessages(lastMessageId);
  186. }
  187. }
  188. checkLoop();
  189. // Stage 6
  190. async function jsonPost(url, data) {
  191. let response=await fetch(url, {
  192. method: 'POST',
  193. body: JSON.stringify(data) })
  194. if (response.ok) {
  195. return await response.json();
  196. } else {
  197. alert("Ошибка HTTP: " + response.status);
  198. }
  199. };