A self hosted chat application with end-to-end encrypted messaging.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

559 lines
21 KiB

  1. // import { sprintf } from 'sprintf-js';
  2. // import { vsprintf } from 'sprintf-js';
  3. /*var off_payment_method = document.getElementsByName('offline_payment_method');
  4. var ischecked_method = false;
  5. for ( var i = 0; i < off_payment_method.length; i++) {
  6. if(off_payment_method[i].checked) {
  7. ischecked_method = true;
  8. break;
  9. }
  10. }
  11. if(!ischecked_method) { //payment method button is not checked
  12. alert("Please choose Offline Payment Method");
  13. }*/
  14. // var css = require('./app.css');
  15. // console.log(css);
  16. // var alertify = require('alertifyjs');
  17. var log = require('loglevel');
  18. var sjcl = require('sjcl');
  19. var markdownit = require('markdown-it');
  20. var md = new markdownit();
  21. var Handlebars = require('handlebars');
  22. var DOMPurify = require('dompurify');
  23. var Fuse = require('fuse.js');
  24. require('./test.js');
  25. var toUserRadios = document.getElementsByName('toUser');
  26. var isCheckedUser = false;
  27. var chatTextArea = document.getElementById('chatTextArea');
  28. var postNewMessageUrl = `http://${hostAddress}/api/chat/post/message`; //hostAddress variable is set in the thymeleaf head fragment
  29. var getAllMessagesUrl = `http://${hostAddress}/api/chat/get/messages/`;
  30. var getNewMessagesUrl = `http://${hostAddress}/api/chat/get/messages/`;
  31. var getActiveUsersUrl = `http://${hostAddress}/api/chat/get/active-users/`;
  32. // var postNewMessageUrl = "http://localhost:8080/api/chat/post/message";
  33. // var getAllMessagesUrl = "http://localhost:8080/api/chat/get/messages/";
  34. // var getNewMessagesUrl = "http://localhost:8080/api/chat/get/messages/";
  35. // var messageLog = [];
  36. var username = localStorage.getItem('username');
  37. var authToken = localStorage.getItem('authToken');
  38. var passphraseInput = document.getElementById('passphrase');
  39. var iterations = 100000;
  40. var source = document.getElementById("msg_container_template").innerHTML;
  41. var msgContainerTemplate = Handlebars.compile(source);
  42. var source = document.getElementById("msg_container_send_template").innerHTML;
  43. var msgContainerSendTemplate = Handlebars.compile(source);
  44. var source = document.getElementById("user-contact-online-template").innerHTML;
  45. var userContactOnlineTemplate = Handlebars.compile(source);
  46. var source = document.getElementById("user-contact-offline-template").innerHTML;
  47. var userContactOfflineTemplate = Handlebars.compile(source);
  48. var chatAreaNew = document.getElementById('chat_area_new');
  49. var userBoxes = document.getElementsByName('user-box');
  50. var activeUsers = {};
  51. var fuseOptions = {
  52. shouldSort: true,
  53. threshold: 0.01,
  54. location: 0,
  55. distance: 100,
  56. maxPatternLength: 32,
  57. minMatchCharLength: 1,
  58. keys: [
  59. "userName",
  60. ]
  61. };
  62. log.setLevel('TRACE');
  63. alertify.set('notifier', 'position', 'top-center');
  64. // Loop through the buttons and add the active class to the current/clicked button
  65. // for (var i = 0; i < btns.length; i++) {
  66. // btns[i].addEventListener("click", function() {
  67. // var current = document.getElementsByClassName("active");
  68. // // If there's no active class
  69. // if (current.length > 0) {
  70. // current[0].className = current[0].className.replace(" active", "");
  71. // }
  72. // // Add the active class to the current/clicked button
  73. // this.className += " active";
  74. // });
  75. // }
  76. getActiveUsers(authToken)
  77. .then(data => {
  78. // activeUsers = data;
  79. sessionStorage.setItem('activeUsers', JSON.stringify(data));
  80. log.log(sessionStorage.getItem('activeUsers'));
  81. })
  82. for (let i = 0; i < userBoxes.length; i++) {
  83. userBoxes[i].addEventListener('click', userCallBack)
  84. }
  85. function addUserCallBacks() {
  86. for (let i = 0; i < userBoxes.length; i++) {
  87. userBoxes[i].addEventListener('click', userCallBack)
  88. }
  89. }
  90. function userCallBack() {
  91. let current = document.getElementsByClassName('user-box active');
  92. let passphrase = passphraseInput.value;
  93. if (current.length > 0) {
  94. if (passphrase == '') {
  95. // alert('Please input passphrase')
  96. alertify.error('Please enter a passphrase');
  97. return;
  98. }
  99. current[0].className = current[0].className.replace(" active", "");
  100. }
  101. // Add the active class to the current/clicked button
  102. else if (current.length == 0) {
  103. let elem = document.getElementById('passphrase-initial');
  104. passphrase = elem.value;
  105. if (passphrase == '') {
  106. // alert('Please input passphrase')
  107. alertify.error('Please enter a passphrase');
  108. return;
  109. }
  110. document.getElementById('no-user-selected').hidden = true;
  111. document.getElementById('chat-card').hidden = false;
  112. elem.hidden = true;
  113. }
  114. // console.log(this.getElementsByClassName('to-user-span'));
  115. let userName = this.getElementsByClassName('to-user-span')[0].innerText;
  116. document.getElementById('user-name-span').innerText = userName;
  117. populateMessages(userName, passphrase);
  118. sessionStorage.setItem('selectedUser', userName);
  119. this.className += " active";
  120. }
  121. function populateMessages(userName, passphrase) {
  122. console.log('Selected user = ' + userName);
  123. if (passphrase == '') {
  124. alert('Please input passphrase')
  125. return;
  126. }
  127. // console.log(userName);
  128. if (sessionStorage.getItem(userName) == null) {
  129. chatTextArea.textContent = '';
  130. chatAreaNew.innerHTML = '';
  131. getAllMessages(userName)
  132. .then(json => {
  133. if (json == null) return;
  134. console.log(json);
  135. let i = 0;
  136. let messageLog = [];
  137. let messageLogNew = [];
  138. let lastMessageTimeStamp;
  139. if (json.length > 0) {
  140. json.forEach(function(obj) {
  141. // console.log(obj.toUser);
  142. messageCipher = JSON.stringify(obj.messageCipher);
  143. console.log(messageCipher);
  144. // let message = sjcl.decrypt("password", messageCipher);
  145. let message = md.render(sjcl.decrypt(passphrase, messageCipher));
  146. let utcDate = obj.messageTime;
  147. lastMessageTimeStamp = utcDate;
  148. let localDate = new Date(utcDate);
  149. let messageLine = sprintf('%s %s: %s ', localDate, obj.fromUser, message);
  150. // localDate.``
  151. // console.log('localDate = ' + localDate);
  152. console.log(messageLine);
  153. // chatTextArea.append(obj.fromUser + ": " + message + "\n");
  154. chatTextArea.append(messageLine + '\n');
  155. messageLog[i++] = messageLine;
  156. chatTextArea.scrollTop = chatTextArea.scrollHeight;
  157. // console.log('Message log = ' + messageLog);
  158. let context = { fromUser: obj.fromUser, message: message, time: localDate.toLocaleString() };
  159. let msgContainer;
  160. if (obj.fromUser == username) {
  161. msgContainer = msgContainerSendTemplate(context);
  162. } else {
  163. msgContainer = msgContainerTemplate(context);
  164. }
  165. messageLogNew.push(JSON.stringify(context));
  166. $(chatAreaNew).append(DOMPurify.sanitize(msgContainer));
  167. });
  168. sessionStorage.setItem(userName, JSON.stringify(messageLog));
  169. sessionStorage.setItem(userName + username + 'new', JSON.stringify(messageLogNew));
  170. // console.log()
  171. // sessionStorage.clear();
  172. console.log('Last message time = ' + lastMessageTimeStamp);
  173. sessionStorage.setItem(userName + '-time', lastMessageTimeStamp);
  174. }
  175. });
  176. } else {
  177. console.log("Stored messages = " + sessionStorage.getItem(userName));
  178. let storedMessages = JSON.parse(sessionStorage.getItem(userName));
  179. let storedMessagesNew = JSON.parse(sessionStorage.getItem(userName + username + 'new'));
  180. let lastMessageTime = sessionStorage.getItem(userName + '-time');
  181. console.log("last message time stamp = " + lastMessageTime);
  182. if (lastMessageTime != null) {
  183. getNewMessages(userName, lastMessageTime)
  184. .then(json => {
  185. if (json == null) return;
  186. console.log(json)
  187. if (json.length > 0) {
  188. json.forEach(function(obj) {
  189. let messageCipher = JSON.stringify(obj.messageCipher);
  190. let message = md.render(sjcl.decrypt(passphrase, messageCipher));
  191. // console.log(message);
  192. // chatTextArea.append(message + "\n");
  193. let utcDate = obj.messageTime;
  194. lastMessageTimeStamp = utcDate;
  195. let localDate = new Date(utcDate);
  196. let messageLine = sprintf('%s %s: %s', localDate, obj.fromUser, message);
  197. // localDate.``
  198. // console.log('localDate = ' + localDate);
  199. console.log(messageLine);
  200. // chatTextArea.append(obj.fromUser + ": " + message + "\n");
  201. chatTextArea.append(messageLine + '\n');
  202. chatTextArea.scrollTop = chatTextArea.scrollHeight;
  203. storedMessages.push(messageLine);
  204. let context = { fromUser: obj.fromUser, message: message, time: localDate.toLocaleString() };
  205. let msgContainer;
  206. if (obj.fromUser == username) {
  207. msgContainer = msgContainerSendTemplate(context);
  208. } else {
  209. msgContainer = msgContainerTemplate(context);
  210. }
  211. storedMessagesNew.push(JSON.stringify(context));
  212. $(chatAreaNew).append(DOMPurify.sanitize(msgContainer));
  213. })
  214. sessionStorage.setItem(userName + '-time', lastMessageTimeStamp);
  215. sessionStorage.setItem(userName, JSON.stringify(storedMessages));
  216. sessionStorage.setItem(userName + username + 'new', JSON.stringify(storedMessagesNew));
  217. console.log("this value stored" + sessionStorage.getItem(userName))
  218. console.log("last message time stamp = " + lastMessageTimeStamp);
  219. console.log(sessionStorage.getItem(userName + '-time'));
  220. }
  221. chatTextArea.textContent = '';
  222. chatAreaNew.innerHTML = '';
  223. console.log("Stored messages 2 = " + storedMessages);
  224. storedMessages.forEach(function(messageLine) {
  225. chatTextArea.append(messageLine + '\n');
  226. chatTextArea.scrollTop = chatTextArea.scrollHeight;
  227. // let context = {message: messageLine};
  228. // let msgContainer;
  229. // if(obj.fromUser == username)
  230. // {
  231. // msgContainer = msgContainerSendTemplate(context);
  232. // }
  233. // else{
  234. // msgContainer = msgContainerTemplate(context);
  235. // }
  236. // $(chatAreaNew).append(msgContainer);
  237. })
  238. storedMessagesNew.forEach(function(contextString) {
  239. let context = JSON.parse(contextString);
  240. let msgContainer;
  241. if (context.fromUser == username) {
  242. msgContainer = msgContainerSendTemplate(context);
  243. } else {
  244. msgContainer = msgContainerTemplate(context);
  245. }
  246. $(chatAreaNew).append(DOMPurify.sanitize(msgContainer));
  247. scrollChatAreaAnimated(2400);
  248. })
  249. });
  250. }
  251. // chatTextArea.append(JSON.stringify(storedMessages));
  252. }
  253. // sessionStorage.setItem('status', 'ready');
  254. // sessionStorage.setItem('userName', messageLog);
  255. // console.log('Message log = ' + messageLog);
  256. // }
  257. // let passphraseKey = userName + '-passphrase';
  258. // sessionStorage.setItem(passphraseKey, passphrase);
  259. // console.log(sessionStorage.getItem(passphraseKey));
  260. }
  261. // var lastMessageTimeStamp;
  262. // console.log(authToken);
  263. // 'Basic ' + btoa("hmm" + ":" + "hmm")
  264. Handlebars.registerHelper('avatar', function() {
  265. return '<div class="img_cont_msg"> <img src="https://static.turbosquid.com/Preview/001292/481/WV/_D.jpg" class="rounded-circle user_img_msg"> </div>';
  266. });
  267. // var user;
  268. function getSelectedUser() {
  269. for (var i = 0; i < toUserRadios.length; i++) {
  270. if (toUserRadios[i].checked) {
  271. let user = toUserRadios[i].value;
  272. console.log('sending to user = ' + user);
  273. isCheckedUser = true;
  274. return user;
  275. }
  276. }
  277. }
  278. function getSelectedUserNew() {
  279. return sessionStorage.getItem('selectedUser');
  280. }
  281. document.getElementById('chatMessageForm').addEventListener('submit', function(e) {
  282. let chatInput = document.getElementById('chatInput');
  283. e.preventDefault();
  284. let user = getSelectedUserNew();
  285. if (!this.checkValidity()) {
  286. console.log("error");
  287. this.classList.add('was-validated');
  288. return;
  289. }
  290. this.classList.add('was-validated');
  291. if (user == null) {
  292. // window.alert('please select a user');
  293. alertify.error('Please select a user');
  294. return;
  295. }
  296. let messageContent = chatInput.value;
  297. let context = { fromUser: username, message: md.render(messageContent), time: new Date().toLocaleString() };
  298. let msgContainer = msgContainerSendTemplate(context);
  299. $(chatAreaNew).append(DOMPurify.sanitize(msgContainer));
  300. scrollChatAreaAnimated(2400);
  301. let messageCipher = sjcl.encrypt(passphraseInput.value, messageContent, { mode: "gcm", ts: 128, adata: "", iter: iterations });
  302. let messageCipherJson = JSON.parse(messageCipher);
  303. let chatMessageDTO = {
  304. "toUser": user,
  305. "messageCipher": messageCipherJson
  306. }
  307. messageSend(JSON.stringify(chatMessageDTO));
  308. })
  309. document.getElementById('user-search').addEventListener('submit', function(e) {
  310. e.preventDefault();
  311. let contactsBox = document.getElementById('contacts-box');
  312. let temp = contactsBox.innerHTML;
  313. // log.trace(temp);
  314. let searchTerm = document.getElementById('user-search-term').value;
  315. log.debug("search term value = " + searchTerm);
  316. let list = JSON.parse(sessionStorage.getItem('activeUsers'));
  317. log.debug("active users");
  318. log.debug(list);
  319. let fuse = new Fuse(list, fuseOptions);
  320. let searchResult = fuse.search(searchTerm);
  321. populateContactsBox(contactsBox, searchResult);
  322. addUserCallBacks();
  323. log.debug(searchResult);
  324. })
  325. document.getElementById('user-search-term').addEventListener('input', function(e) {
  326. e.preventDefault();
  327. if (this.value.length < 2) {
  328. log.debug("inputted")
  329. let cancelButton = document.getElementById('user-search-cancel');
  330. cancelButton.hidden = false;
  331. }
  332. })
  333. document.getElementById('user-search-cancel').addEventListener('click', function(e) {
  334. e.preventDefault();
  335. let list = JSON.parse(sessionStorage.getItem('activeUsers'));
  336. let contactsBox = document.getElementById('contacts-box');
  337. populateContactsBox(contactsBox,list);
  338. addUserCallBacks();
  339. document.getElementById('user-search-term').value = "";
  340. this.hidden = true;
  341. })
  342. function populateContactsBox(contactsBox, list)
  343. {
  344. let userContactBoxList = "";
  345. list.forEach(function(activeUser) {
  346. log.debug(activeUser);
  347. if (activeUser.online) {
  348. userContactBoxList += userContactOnlineTemplate(activeUser);
  349. } else {
  350. userContactBoxList += userContactOfflineTemplate(activeUser);
  351. }
  352. })
  353. contactsBox.innerHTML = userContactBoxList;
  354. }
  355. // console.log('Credentials = ' + JSON.parse(sessionStorage.getItem('credentials')));
  356. function messageSend(chatMessageDTO) {
  357. let headers = new Headers();
  358. // console.log("Token = " + btoa("hmm" + ":" + "hmm"))
  359. // headers.append('Accept','application/json')
  360. headers.append('Content-Type', 'application/json');
  361. // headers.append('Authorization', basicAuthToken);
  362. headers.append('X-AUTH-TOKEN', authToken);
  363. fetch(postNewMessageUrl, {
  364. method: 'POST',
  365. headers: headers,
  366. body: chatMessageDTO
  367. })
  368. .then(response => {
  369. console.log(response);
  370. return response.clone();
  371. })
  372. .then(response => fetchHandler(response));
  373. }
  374. async function getAllMessages(toUser) {
  375. let headers = new Headers();
  376. // headers.append('Accept','application/json')
  377. // headers.append('Content-Type', 'application/json');
  378. // headers.append('Authorization', basicAuthToken);
  379. headers.append('X-AUTH-TOKEN', authToken);
  380. let response = await fetch(getAllMessagesUrl + toUser, {
  381. method: 'GET',
  382. headers: headers
  383. });
  384. console.log(response);
  385. if (fetchErrorHandler(response.clone())) {
  386. return null;
  387. }
  388. // if (response.status == 440) {
  389. // window.alert('Token has expired. Please login again');
  390. // return null;
  391. // }
  392. let data = await response.json();
  393. return data;
  394. }
  395. async function getNewMessages(toUser, lastMessageTimeStamp) {
  396. let headers = new Headers();
  397. // headers.append('Authorization', basicAuthToken);
  398. headers.append('X-AUTH-TOKEN', authToken);
  399. let response = await fetch(`${getNewMessagesUrl}${toUser}/${lastMessageTimeStamp}`, {
  400. method: 'GET',
  401. headers: headers
  402. });
  403. console.log(response.clone());
  404. if (fetchErrorHandler(response.clone())) {
  405. return null;
  406. }
  407. let data = await response.json();
  408. return data;
  409. }
  410. async function getActiveUsers(authToken2) {
  411. let headers = new Headers();
  412. // headers.append('Authorization', basicAuthToken);
  413. headers.append('X-AUTH-TOKEN', authToken2);
  414. let response = await fetch(getActiveUsersUrl, {
  415. method: 'GET',
  416. headers: headers
  417. });
  418. console.log(response.clone());
  419. if (fetchErrorHandler(response.clone())) {
  420. return null;
  421. }
  422. let data = await response.json();
  423. return data;
  424. }
  425. $(document).ready(function() {
  426. $('#action_menu_btn').click(function() {
  427. $('.action_menu').toggle();
  428. });
  429. });
  430. function fetchHandler(response) {
  431. if (response.ok) {
  432. return response.json().then(json => {
  433. // the status was ok and there is a json body
  434. // return Promise.resolve({ json: json, response: response });
  435. alertify.success('Message sent succesfully' + sprintf(" (http code %d)", response.status));
  436. }).catch(err => {
  437. // the status was ok but there is no json body
  438. // return Promise.resolve({ response: response });
  439. alertify.success('Message sent succesfully' + sprintf(" (http code %d)", response.status));
  440. });
  441. } else {
  442. return response.json().catch(err => {
  443. // the status was not ok and there is no json body
  444. // throw new Error(response.statusText);
  445. alertify.error('Some error occured. Please try again.');
  446. }).then(json => {
  447. // the status was not ok but there is a json body
  448. // throw new Error(json.error.message); // example error message returned by a REST
  449. let delay = alertify.get('notifier', 'delay');
  450. alertify.set('notifier', 'delay', 30);
  451. let errorMessage = "";
  452. json.errors.forEach(function(data) {
  453. errorMessage += sprintf("Field Name: %s \n Rejected value: %s \n Reason: %s \n", data.field_name, data.rejected_value, data.error_message);
  454. });
  455. alertify.error(sprintf('There were errors in your message - %s', errorMessage));
  456. alertify.set('notifier', 'delay', delay);
  457. });
  458. }
  459. }
  460. function fetchErrorHandler(response) {
  461. // alertify.success('Current position : ' + alertify.get('notifier', 'position'));
  462. if (!response.ok) {
  463. return response.text().catch(err => {
  464. // the status was not ok and there is no json body
  465. // throw new Error(response.statusText);
  466. // window.alert(sprintf('Some error occured. Http code is %s', response.status));
  467. alertify.error(sprintf('Some error occured. Http code is %s', response.status));
  468. return true;
  469. }).then(json => {
  470. // the status was not ok but there is a json body
  471. // throw new Error(json.error.message); // example error message returned by a REST API
  472. // window.alert(sprintf('Error: %s (Http code %s)', json, response.status));
  473. alertify.error(sprintf('Some error occured. Http code is %s', response.status));
  474. console.log(json);
  475. return true;
  476. });
  477. }
  478. }
  479. function scrollChatAreaAnimated(delay) {
  480. $(chatAreaNew).stop().animate({
  481. scrollTop: $(chatAreaNew)[0].scrollHeight
  482. }, delay);
  483. }