index.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>P2P chat</title>
  6. <base href="" target="_top" id="base">
  7. <script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, ""); </script>
  8. <link rel="stylesheet" href="css/layout.css">
  9. <link rel="stylesheet" href="css/normalize.css">
  10. </head>
  11. <body>
  12. <p>
  13. <button onclick="login()">Login / Change certificate</button>
  14. </p>
  15. <div>
  16. Version / Timestamp: 14<br>
  17. <p>
  18. <b>About</b>
  19. This is a simple peer-to-peer chat which uses the PeerMessage
  20. plugin. After selecting a certificate, you will be displayed to
  21. others. You can add them as a "friend". This means that you can
  22. exchange messages with them. Adding as a friend is necessary on
  23. both sides. Messages that are not from a friend will be discarded.
  24. To disguise the recipient of a message, it will be forwarded even
  25. if it is meant for you. The sender of a message can be identified
  26. by the public key. The messages are encrypted using the
  27. CryptMessage plugin and Ecies.
  28. </p>
  29. </div>
  30. <br>
  31. <div id="contactContainer">
  32. Contacts: <div class="waitingDot" id="waitingForContact"></div>
  33. <ul id="contactList">
  34. </ul>
  35. </div>
  36. <div id="chat_window">
  37. <input type="text" id="chat_input" autocomplete="off"> <button onclick="sendChatMessage()" id="sendMessageButton">Send</button>
  38. <div id="chat">
  39. </div>
  40. </div>
  41. <script type="text/javascript" src="js/ZeroFrame.js"></script>
  42. <script>
  43. const zf = new ZeroFrame();
  44. const id_providers = ["cryptoid.bit", "kaffie.bit", "nobody", "zeropolska.bit"];
  45. const keyMap = new Map();
  46. const msgMap = new Map();
  47. let announceIntervalId = 0;
  48. let activeTab = null;
  49. zf.onRequest = (cmd, msg) => {
  50. if (cmd === "peerReceive") {
  51. if (msg.params.cert === "" || msg.params.cert === undefined || msg.params.message === null) {
  52. /* throw message away when it's invalid */
  53. zf.cmd("peerInvalid", [msg.params.hash]);
  54. } else if (msg.params.message.type === "announceKey") {
  55. /* you get a key from another person */
  56. if (msg.params.ip !== "self" && ! keyMap.has(msg.params.signed_by)) {
  57. /* make the red ball blue for a second */
  58. const notifyElement = document.getElementById("waitingForContact");
  59. notifyElement.style.backgroundColor = "blue";
  60. setTimeout(() => { notifyElement.style.backgroundColor = "red"; }, 1000);
  61. /* add key and reload contact list */
  62. keyMap.set(msg.params.signed_by, [msg.params.cert, msg.params.message.value[0]]);
  63. reloadContacts();
  64. }
  65. zf.cmd("peerValid", [msg.params.hash]);
  66. } else if (msg.params.message.type === "message") {
  67. /* to disguise the recipient, send the message even if it is for you */
  68. /* and it enables multi-client support */
  69. zf.cmd("peerValid", [msg.params.hash]);
  70. if (msg.params.ip !== "self") {
  71. zf.cmd("eciesDecrypt", [msg.params.message.value[0]], (decrypted) => {
  72. // console.log("Receive message", decrypted, "from", msg.params.cert);
  73. if (decrypted !== null && msgMap.has(msg.params.signed_by)) {
  74. /* when message can be decrypted and the contact it marked as friend */
  75. pushMessage(msg.params.signed_by, decrypted, "peer");
  76. if (activeTab !== msg.params.signed_by)
  77. zf.cmd("wrapperNotification", ["info", "New message from " + getNickname(keyMap.get(msg.params.signed_by)[0], msg.params.signed_by), 2500]);
  78. }
  79. });
  80. }
  81. }
  82. }
  83. };
  84. zf.cmd("siteInfo", [], (info) => {
  85. /* ask the user to select a cert when noone is selected */
  86. if (info.cert_user_id === null) {
  87. login();
  88. } else {
  89. reloadAnnounceInterval(info);
  90. }
  91. });
  92. /* register: send message on enter */
  93. document.getElementById("chat_input").addEventListener("keyup", (event) => {
  94. // Number 13 is the "Enter" key on the keyboard
  95. if (event.keyCode === 13) {
  96. event.preventDefault();
  97. /* simulate click on button */
  98. document.getElementById("sendMessageButton").click();
  99. }
  100. });
  101. /* activate / deactivate the announceKey Interval depend
  102. * on when a cert if selected */
  103. function reloadAnnounceInterval(info) {
  104. if (info.cert_user_id !== null) {
  105. announceIntervalId = window.setInterval(announceKey, 5000);
  106. } else {
  107. clearInterval(announceIntervalId);
  108. }
  109. }
  110. /* encode html to prevent html/js injection */
  111. function htmlEncode(str) {
  112. return String(str).replace(/[^\w. ]/gi, (c) => {
  113. return '&#' + c.charCodeAt(0) + ';';
  114. });
  115. }
  116. function createChatEntry(message) {
  117. let html = "<pre><i>" + (message.sender === "self" ? "You" : "Peer") + "</i>: ";
  118. let chatMessage = htmlEncode(message.message);
  119. if (chatMessage === "") {
  120. chatMessage = "<i>[empty message]</i>";
  121. }
  122. html += chatMessage + "</pre><br>\n";
  123. return html;
  124. }
  125. /* show a chat history */
  126. function showTab(signed_by) {
  127. const htmlChat = document.getElementById("chat");
  128. const msgs = msgMap.get(signed_by);
  129. if (activeTab === signed_by) {
  130. if (msgs.length > 0) {
  131. htmlChat.innerHTML = createChatEntry(msgs[msgs.length - 1]) + htmlChat.innerHTML;
  132. }
  133. } else {
  134. activeTab = signed_by;
  135. reloadContacts();
  136. htmlChat.innerHTML = "";
  137. for (let i = msgs.length - 1; i >= 0; i--) {
  138. htmlChat.innerHTML += createChatEntry(msgs[i]);
  139. }
  140. }
  141. }
  142. /* generate a more or less unique nickname */
  143. function getNickname(cert, signed_by) {
  144. let nick = cert.split("/");
  145. /* in case the cert does not confirm the standard */
  146. if (nick.length === 0 || nick.length > 2)
  147. nick = cert;
  148. else
  149. nick = nick[1];
  150. return nick + '#' + signed_by.substring(1, 5);
  151. }
  152. function addFriend(signed_by) {
  153. if (! hasFriend(signed_by)) {
  154. msgMap.set(signed_by, []);
  155. reloadContacts();
  156. }
  157. }
  158. function pushMessage(friend, message, sender) {
  159. msgMap.get(friend).push({ sender: sender, message: message });
  160. if (activeTab === friend)
  161. showTab(friend);
  162. }
  163. function hasFriend(signed_by) {
  164. return msgMap.has(signed_by);
  165. }
  166. function sendChatMessage() {
  167. if (activeTab === null) {
  168. /* in case no contact is selected */
  169. zf.cmd("wrapperNotification", ["info", "Please select a contact to send a message."]);
  170. } else {
  171. const inputHtml = document.getElementById("chat_input");
  172. if (inputHtml.value !== "") {
  173. /* do not send when the message it empty */
  174. sendMessage(activeTab, inputHtml.value);
  175. inputHtml.value = "";
  176. }
  177. }
  178. }
  179. function sendMessage(signed_by, input) {
  180. let friend_info = keyMap.get(signed_by);
  181. addFriend(signed_by);
  182. pushMessage(signed_by, input, "self");
  183. zf.cmd("eciesEncrypt", [input, friend_info[1]], (encrpyted) => {
  184. const msg = {
  185. type: "message",
  186. value: [encrpyted]
  187. };
  188. zf.cmd("peerBroadcast", [msg, false, 10], (sent) => {
  189. if (! sent.sent) {
  190. zf.cmd("wrapperNotification", ["error", "Error while send message: " + sent.sent]);
  191. }
  192. });
  193. });
  194. }
  195. function reloadContacts() {
  196. const html_list = document.getElementById("contactList");
  197. html_list.innerHTML = "";
  198. keyMap.forEach((value, key, map) => {
  199. if (activeTab === key) {
  200. /* the contact is currectly selected */
  201. button = "(You see it already.)";
  202. } else if (hasFriend(key)) {
  203. /* the contact is a friend */
  204. let onclick = "showTab('" + htmlEncode(key) + "')";
  205. var button = "<button onclick=\"" + onclick + "\">" + "See it" + "</button>";
  206. } else {
  207. /* it is just a contact */
  208. let onclick = "addFriend('" + htmlEncode(key) + "')";
  209. var button = "<button onclick=\"" + onclick + "\">" + "Add as friend" + "</button>";
  210. }
  211. html_list.innerHTML += "<li>" + htmlEncode(getNickname(value[0], key)) + " " + button + "</li>" + "\n";
  212. });
  213. }
  214. function login() {
  215. zf.cmd("certSelect", {accepted_domains: id_providers}, (ok) => {
  216. if (ok !== "ok") {
  217. zf.cmd("wrapperNotification", ["error", "Error while selecting the certificate: " + ok]);
  218. }
  219. zf.cmd("siteInfo", [], (info) => {
  220. reloadAnnounceInterval(info);
  221. });
  222. });
  223. }
  224. /* broadcast my key and nickname to other peers */
  225. function announceKey() {
  226. zf.cmd("userPublickey", [], (key) => {
  227. const msg = {
  228. type: "announceKey",
  229. value: [key]
  230. };
  231. zf.cmd("peerBroadcast", [msg, false, 10], (sent) => {
  232. if (! sent.sent) {
  233. zf.cmd("wrapperNotification", ["error", "Error while announce key: " + sent.sent]);
  234. }
  235. // else console.log("Announce key success");
  236. });
  237. });
  238. }
  239. </script>
  240. </body>
  241. </html>