123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>P2P chat</title>
- <base href="" target="_top" id="base">
- <script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, ""); </script>
-
- <link rel="stylesheet" href="css/layout.css">
- <link rel="stylesheet" href="css/normalize.css">
- </head>
- <body>
- <p>
- <button onclick="login()">Login / Change certificate</button>
- </p>
-
- <div>
- Version / Timestamp: 14<br>
- <p>
- <b>About</b>
- This is a simple peer-to-peer chat which uses the PeerMessage
- plugin. After selecting a certificate, you will be displayed to
- others. You can add them as a "friend". This means that you can
- exchange messages with them. Adding as a friend is necessary on
- both sides. Messages that are not from a friend will be discarded.
- To disguise the recipient of a message, it will be forwarded even
- if it is meant for you. The sender of a message can be identified
- by the public key. The messages are encrypted using the
- CryptMessage plugin and Ecies.
- </p>
- </div>
-
- <br>
- <div id="contactContainer">
- Contacts: <div class="waitingDot" id="waitingForContact"></div>
- <ul id="contactList">
-
- </ul>
- </div>
-
- <div id="chat_window">
- <input type="text" id="chat_input" autocomplete="off"> <button onclick="sendChatMessage()" id="sendMessageButton">Send</button>
- <div id="chat">
-
- </div>
- </div>
- <script type="text/javascript" src="js/ZeroFrame.js"></script>
- <script>
- const zf = new ZeroFrame();
- const id_providers = ["cryptoid.bit", "kaffie.bit", "nobody", "zeropolska.bit"];
- const keyMap = new Map();
- const msgMap = new Map();
- let announceIntervalId = 0;
- let activeTab = null;
-
- zf.onRequest = (cmd, msg) => {
- if (cmd === "peerReceive") {
- if (msg.params.cert === "" || msg.params.cert === undefined || msg.params.message === null) {
- /* throw message away when it's invalid */
- zf.cmd("peerInvalid", [msg.params.hash]);
- } else if (msg.params.message.type === "announceKey") {
- /* you get a key from another person */
- if (msg.params.ip !== "self" && ! keyMap.has(msg.params.signed_by)) {
- /* make the red ball blue for a second */
- const notifyElement = document.getElementById("waitingForContact");
- notifyElement.style.backgroundColor = "blue";
- setTimeout(() => { notifyElement.style.backgroundColor = "red"; }, 1000);
-
- /* add key and reload contact list */
- keyMap.set(msg.params.signed_by, [msg.params.cert, msg.params.message.value[0]]);
- reloadContacts();
- }
- zf.cmd("peerValid", [msg.params.hash]);
- } else if (msg.params.message.type === "message") {
- /* to disguise the recipient, send the message even if it is for you */
- /* and it enables multi-client support */
- zf.cmd("peerValid", [msg.params.hash]);
- if (msg.params.ip !== "self") {
- zf.cmd("eciesDecrypt", [msg.params.message.value[0]], (decrypted) => {
- // console.log("Receive message", decrypted, "from", msg.params.cert);
- if (decrypted !== null && msgMap.has(msg.params.signed_by)) {
- /* when message can be decrypted and the contact it marked as friend */
- pushMessage(msg.params.signed_by, decrypted, "peer");
- if (activeTab !== msg.params.signed_by)
- zf.cmd("wrapperNotification", ["info", "New message from " + getNickname(keyMap.get(msg.params.signed_by)[0], msg.params.signed_by), 2500]);
- }
- });
- }
- }
- }
- };
-
-
- zf.cmd("siteInfo", [], (info) => {
- /* ask the user to select a cert when noone is selected */
- if (info.cert_user_id === null) {
- login();
- } else {
- reloadAnnounceInterval(info);
- }
- });
-
- /* register: send message on enter */
- document.getElementById("chat_input").addEventListener("keyup", (event) => {
- // Number 13 is the "Enter" key on the keyboard
- if (event.keyCode === 13) {
- event.preventDefault();
- /* simulate click on button */
- document.getElementById("sendMessageButton").click();
- }
- });
-
- /* activate / deactivate the announceKey Interval depend
- * on when a cert if selected */
- function reloadAnnounceInterval(info) {
- if (info.cert_user_id !== null) {
- announceIntervalId = window.setInterval(announceKey, 5000);
- } else {
- clearInterval(announceIntervalId);
- }
- }
-
- /* encode html to prevent html/js injection */
- function htmlEncode(str) {
- return String(str).replace(/[^\w. ]/gi, (c) => {
- return '&#' + c.charCodeAt(0) + ';';
- });
- }
-
- function createChatEntry(message) {
- let html = "<pre><i>" + (message.sender === "self" ? "You" : "Peer") + "</i>: ";
- let chatMessage = htmlEncode(message.message);
- if (chatMessage === "") {
- chatMessage = "<i>[empty message]</i>";
- }
- html += chatMessage + "</pre><br>\n";
-
- return html;
- }
-
- /* show a chat history */
- function showTab(signed_by) {
- const htmlChat = document.getElementById("chat");
- const msgs = msgMap.get(signed_by);
-
- if (activeTab === signed_by) {
- if (msgs.length > 0) {
- htmlChat.innerHTML = createChatEntry(msgs[msgs.length - 1]) + htmlChat.innerHTML;
- }
- } else {
- activeTab = signed_by;
- reloadContacts();
- htmlChat.innerHTML = "";
- for (let i = msgs.length - 1; i >= 0; i--) {
- htmlChat.innerHTML += createChatEntry(msgs[i]);
- }
- }
- }
-
- /* generate a more or less unique nickname */
- function getNickname(cert, signed_by) {
- let nick = cert.split("/");
- /* in case the cert does not confirm the standard */
- if (nick.length === 0 || nick.length > 2)
- nick = cert;
- else
- nick = nick[1];
-
- return nick + '#' + signed_by.substring(1, 5);
- }
-
- function addFriend(signed_by) {
- if (! hasFriend(signed_by)) {
- msgMap.set(signed_by, []);
- reloadContacts();
- }
- }
-
- function pushMessage(friend, message, sender) {
- msgMap.get(friend).push({ sender: sender, message: message });
- if (activeTab === friend)
- showTab(friend);
- }
-
- function hasFriend(signed_by) {
- return msgMap.has(signed_by);
- }
-
- function sendChatMessage() {
- if (activeTab === null) {
- /* in case no contact is selected */
- zf.cmd("wrapperNotification", ["info", "Please select a contact to send a message."]);
- } else {
- const inputHtml = document.getElementById("chat_input");
- if (inputHtml.value !== "") {
- /* do not send when the message it empty */
- sendMessage(activeTab, inputHtml.value);
- inputHtml.value = "";
- }
- }
- }
-
- function sendMessage(signed_by, input) {
- let friend_info = keyMap.get(signed_by);
- addFriend(signed_by);
- pushMessage(signed_by, input, "self");
- zf.cmd("eciesEncrypt", [input, friend_info[1]], (encrpyted) => {
- const msg = {
- type: "message",
- value: [encrpyted]
- };
- zf.cmd("peerBroadcast", [msg, false, 10], (sent) => {
- if (! sent.sent) {
- zf.cmd("wrapperNotification", ["error", "Error while send message: " + sent.sent]);
- }
- });
- });
- }
-
- function reloadContacts() {
- const html_list = document.getElementById("contactList");
- html_list.innerHTML = "";
- keyMap.forEach((value, key, map) => {
-
- if (activeTab === key) {
- /* the contact is currectly selected */
- button = "(You see it already.)";
- } else if (hasFriend(key)) {
- /* the contact is a friend */
- let onclick = "showTab('" + htmlEncode(key) + "')";
- var button = "<button onclick=\"" + onclick + "\">" + "See it" + "</button>";
- } else {
- /* it is just a contact */
- let onclick = "addFriend('" + htmlEncode(key) + "')";
- var button = "<button onclick=\"" + onclick + "\">" + "Add as friend" + "</button>";
- }
-
- html_list.innerHTML += "<li>" + htmlEncode(getNickname(value[0], key)) + " " + button + "</li>" + "\n";
- });
- }
-
- function login() {
- zf.cmd("certSelect", {accepted_domains: id_providers}, (ok) => {
- if (ok !== "ok") {
- zf.cmd("wrapperNotification", ["error", "Error while selecting the certificate: " + ok]);
- }
- zf.cmd("siteInfo", [], (info) => {
- reloadAnnounceInterval(info);
- });
- });
- }
-
- /* broadcast my key and nickname to other peers */
- function announceKey() {
- zf.cmd("userPublickey", [], (key) => {
- const msg = {
- type: "announceKey",
- value: [key]
- };
- zf.cmd("peerBroadcast", [msg, false, 10], (sent) => {
- if (! sent.sent) {
- zf.cmd("wrapperNotification", ["error", "Error while announce key: " + sent.sent]);
- }
- // else console.log("Announce key success");
- });
- });
- }
-
- </script>
- </body>
- </html>
|