diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b9d905e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +*.gitattributes linguist-vendored +util/mongoose.c linguist-vendored +util/mongoose.h linguist-vendored +util/websocket_chatExample/* linguist-vendored + +.gitattributes export-ignore +screenshots/* export-ignore +util/* export-ignore diff --git a/app/app.js b/app/app.js index d57d5c5..1d0a5cd 100644 --- a/app/app.js +++ b/app/app.js @@ -1,554 +1,711 @@ - window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange; - + if (!window.indexedDB) { - window.alert("Your browser doesn't support a stable version of IndexedDB.") + window.alert("Your browser doesn't support a stable version of IndexedDB.") } -var contacts = []; -var receiverID,selfID,recStat; -var selfwebsocket,receiverWebSocket,receiverSuperNodeWS; +var contacts, groups; +var searchIndex = new FlexSearch(); +var receiverID, selfID, recStat, modSuperNode, msgType; +var selfwebsocket, receiverWebSocket; var privKey; -var encrypt = { +var floOpt = { - p: BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16), + p: BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16), - exponent1: function () { - return encrypt.p.add(BigInteger.ONE).divide(BigInteger("4")) - }, + exponent1: function () { + return floOpt.p.add(BigInteger.ONE).divide(BigInteger("4")) + }, - calculateY: function (x) { - let p = this.p; - let exp = this.exponent1(); - // x is x value of public key in BigInteger format without 02 or 03 or 04 prefix - return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p) - }, + calculateY: function (x) { + let p = this.p; + let exp = this.exponent1(); + // x is x value of public key in BigInteger format without 02 or 03 or 04 prefix + return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p) + }, - // Insert a compressed public key - getUncompressedPublicKey: function (compressedPublicKey) { + // Insert a compressed public key + getUncompressedPublicKey: function (compressedPublicKey) { - const p = this.p; + const p = this.p; - // Fetch x from compressedPublicKey - let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey); - const prefix = pubKeyBytes.shift() // remove prefix - let prefix_modulus = prefix % 2; - pubKeyBytes.unshift(0) // add prefix 0 - let x = new BigInteger(pubKeyBytes) - let xDecimalValue = x.toString() + // Fetch x from compressedPublicKey + let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey); + const prefix = pubKeyBytes.shift() // remove prefix + let prefix_modulus = prefix % 2; + pubKeyBytes.unshift(0) // add prefix 0 + let x = new BigInteger(pubKeyBytes) + let xDecimalValue = x.toString() - // Fetch y - let y = this.calculateY(x); - let yDecimalValue = y.toString(); + // Fetch y + let y = this.calculateY(x); + let yDecimalValue = y.toString(); - // verify y value - let resultBigInt = y.mod(BigInteger("2")); + // verify y value + let resultBigInt = y.mod(BigInteger("2")); - let check = resultBigInt.toString() % 2; + let check = resultBigInt.toString() % 2; - if (prefix_modulus !== check) { - yDecimalValue = y.negate().mod(p).toString(); - } + if (prefix_modulus !== check) { + yDecimalValue = y.negate().mod(p).toString(); + } - return { - x: xDecimalValue, - y: yDecimalValue - }; - }, + return { + x: xDecimalValue, + y: yDecimalValue + }; + }, - getSenderPublicKeyString: function () { - privateKey = ellipticCurveEncryption.senderRandom(); - senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey); - return { - privateKey: privateKey, - senderPublicKeyString: senderPublicKeyString - } - }, + getSenderPublicKeyString: function () { + privateKey = ellipticCurveEncryption.senderRandom(); + senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey); + return { + privateKey: privateKey, + senderPublicKeyString: senderPublicKeyString + } + }, - deriveSharedKeySender: function (receiverCompressedPublicKey, senderPrivateKey) { - try { - let receiverPublicKeyString = this.getUncompressedPublicKey( - receiverCompressedPublicKey); - var senderDerivedKey = { - XValue: "", - YValue: "" - }; - senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation( - receiverPublicKeyString.x, - receiverPublicKeyString.y, senderPrivateKey); - return senderDerivedKey; - } catch (error) { - return new Error(error); - } - }, + deriveSharedKeySender: function (receiverCompressedPublicKey, senderPrivateKey) { + try { + let receiverPublicKeyString = this.getUncompressedPublicKey( + receiverCompressedPublicKey); + var senderDerivedKey = { + XValue: "", + YValue: "" + }; + senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation( + receiverPublicKeyString.x, + receiverPublicKeyString.y, senderPrivateKey); + return senderDerivedKey; + } catch (error) { + return new Error(error); + } + }, - deriveReceiverSharedKey: function (senderPublicKeyString, receiverPrivateKey) { - return ellipticCurveEncryption.receiverSharedKeyDerivation( - senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, - receiverPrivateKey); - }, + deriveReceiverSharedKey: function (senderPublicKeyString, receiverPrivateKey) { + return ellipticCurveEncryption.receiverSharedKeyDerivation( + senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, + receiverPrivateKey); + }, - getReceiverPublicKeyString: function (privateKey) { - return ellipticCurveEncryption.receiverPublicString(privateKey); - }, + getReceiverPublicKeyString: function (privateKey) { + return ellipticCurveEncryption.receiverPublicString(privateKey); + }, - deriveSharedKeyReceiver: function (senderPublicKeyString, receiverPrivateKey) { - try { - return ellipticCurveEncryption.receiverSharedKeyDerivation(senderPublicKeyString.XValuePublicString, - senderPublicKeyString.YValuePublicString, receiverPrivateKey); + deriveSharedKeyReceiver: function (senderPublicKeyString, receiverPrivateKey) { + try { + return ellipticCurveEncryption.receiverSharedKeyDerivation(senderPublicKeyString.XValuePublicString, + senderPublicKeyString.YValuePublicString, receiverPrivateKey); - } catch (error) { - return new Error(error); - } - }, + } catch (error) { + return new Error(error); + } + }, - encryptMessage: function (data, receiverCompressedPublicKey) { - var senderECKeyData = this.getSenderPublicKeyString(); - var senderDerivedKey = { - XValue: "", - YValue: "" - }; - var senderPublicKeyString = {}; - senderDerivedKey = this.deriveSharedKeySender( - receiverCompressedPublicKey, senderECKeyData.privateKey); - console.log("senderDerivedKey", senderDerivedKey); - let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue; - let secret = Crypto.AES.encrypt(data, senderKey); - return { - secret: secret, - senderPublicKeyString: senderECKeyData.senderPublicKeyString - }; - }, + encryptData: function (data, receiverCompressedPublicKey) { + var senderECKeyData = this.getSenderPublicKeyString(); + var senderDerivedKey = { + XValue: "", + YValue: "" + }; + var senderPublicKeyString = {}; + senderDerivedKey = this.deriveSharedKeySender( + receiverCompressedPublicKey, senderECKeyData.privateKey); + console.log("senderDerivedKey", senderDerivedKey); + let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue; + let secret = Crypto.AES.encrypt(data, senderKey); + return { + secret: secret, + pubVal: senderECKeyData.senderPublicKeyString + }; + }, - decryptMessage: function (secret, senderPublicKeyString) { - var receiverDerivedKey = { - XValue: "", - YValue: "" - }; - var receiverECKeyData = {}; - var myPrivateKey = privKey; - if (typeof myPrivateKey !== "string") throw new Error("No private key found."); + decryptData: function (secret, senderPublicKeyString, myPrivateKey) { + var receiverDerivedKey = { + XValue: "", + YValue: "" + }; + var receiverECKeyData = {}; + if (typeof myPrivateKey !== "string") throw new Error("No private key found."); - let privateKey = this.wifToDecimal(myPrivateKey, true); - if (typeof privateKey.privateKeyDecimal !== "string") throw new Error( - "Failed to detremine your private key."); - receiverECKeyData.privateKey = privateKey.privateKeyDecimal; + let privateKey = this.wifToDecimal(myPrivateKey, true); + if (typeof privateKey.privateKeyDecimal !== "string") throw new Error( + "Failed to detremine your private key."); + receiverECKeyData.privateKey = privateKey.privateKeyDecimal; - receiverDerivedKey = this.deriveReceiverSharedKey(senderPublicKeyString, - receiverECKeyData.privateKey); - console.log("receiverDerivedKey", receiverDerivedKey); + receiverDerivedKey = this.deriveReceiverSharedKey(senderPublicKeyString, + receiverECKeyData.privateKey); + console.log("receiverDerivedKey", receiverDerivedKey); - let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue; - let decryptMsg = Crypto.AES.decrypt(secret, receiverKey); - return decryptMsg; - }, + let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue; + let decryptMsg = Crypto.AES.decrypt(secret, receiverKey); + return decryptMsg; + }, - ecparams: EllipticCurve.getSECCurveByName("secp256k1"), - getPubKeyHex: function(privateKeyHex){ - var key = new Bitcoin.ECKey(privateKeyHex); - if(key.priv == null){ - alert("Invalid Private key"); - return; - } - key.setCompressed(true); - var pubkeyHex = key.getPubKeyHex(); - return pubkeyHex; - }, - getFLOIDfromPubkeyHex: function(pubkeyHex){ - var key = new Bitcoin.ECKey().setPub(pubkeyHex); - var floID = key.getBitcoinAddress(); - return floID; - }, - sign: function (msg, privateKeyHex) { - var key = new Bitcoin.ECKey(privateKeyHex); - key.setCompressed(true); + ecparams: EllipticCurve.getSECCurveByName("secp256k1"), + getPubKeyHex: function (privateKeyHex) { + var key = new Bitcoin.ECKey(privateKeyHex); + if (key.priv == null) { + alert("Invalid Private key"); + return; + } + key.setCompressed(true); + var pubkeyHex = key.getPubKeyHex(); + return pubkeyHex; + }, + getFLOIDfromPubkeyHex: function (pubkeyHex) { + var key = new Bitcoin.ECKey().setPub(pubkeyHex); + var floID = key.getBitcoinAddress(); + return floID; + }, + signData: function (data, privateKeyHex) { + var key = new Bitcoin.ECKey(privateKeyHex); + key.setCompressed(true); - var privateKeyArr = key.getBitcoinPrivateKeyByteArray(); - privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr); - var messageHash = Crypto.SHA256(msg); + var privateKeyArr = key.getBitcoinPrivateKeyByteArray(); + privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr); + var messageHash = Crypto.SHA256(data); - var messageHashBigInteger = new BigInteger(messageHash); - var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv); + var messageHashBigInteger = new BigInteger(messageHash); + var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv); - var sighex = Crypto.util.bytesToHex(messageSign); - return sighex; - }, - verify: function (msg, signatureHex, publicKeyHex) { - var msgHash = Crypto.SHA256(msg); - var messageHashBigInteger = new BigInteger(msgHash); + var sighex = Crypto.util.bytesToHex(messageSign); + return sighex; + }, + verifyData: function (data, signatureHex, publicKeyHex) { + var msgHash = Crypto.SHA256(data); + var messageHashBigInteger = new BigInteger(msgHash); - var sigBytes = Crypto.util.hexToBytes(signatureHex); - var signature = Bitcoin.ECDSA.parseSig(sigBytes); + var sigBytes = Crypto.util.hexToBytes(signatureHex); + var signature = Bitcoin.ECDSA.parseSig(sigBytes); - var publicKeyPoint = this.ecparams.getCurve().decodePointHex(publicKeyHex); + var publicKeyPoint = this.ecparams.getCurve().decodePointHex(publicKeyHex); - var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s, - publicKeyPoint); - return verify; - }, - wifToDecimal: function(pk_wif, isPubKeyCompressed = false) { - let pk = Bitcoin.Base58.decode(pk_wif) - pk.shift() - pk.splice(-4, 4) - //If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01). - if (isPubKeyCompressed == true) pk.pop() - pk.unshift(0) - privateKeyDecimal = BigInteger(pk).toString() - privateKeyHex = Crypto.util.bytesToHex(pk) - return { - privateKeyDecimal: privateKeyDecimal, - privateKeyHex: privateKeyHex - } - } + var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s, + publicKeyPoint); + return verify; + }, + wifToDecimal: function (pk_wif, isPubKeyCompressed = false) { + let pk = Bitcoin.Base58.decode(pk_wif) + pk.shift() + pk.splice(-4, 4) + //If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01). + if (isPubKeyCompressed == true) pk.pop() + pk.unshift(0) + privateKeyDecimal = BigInteger(pk).toString() + privateKeyHex = Crypto.util.bytesToHex(pk) + return { + privateKeyDecimal: privateKeyDecimal, + privateKeyHex: privateKeyHex + } + }, + genNewIDpair: function () { + try { + var key = new Bitcoin.ECKey(false); + key.setCompressed(true); + return { + floID: key.getBitcoinAddress(), + pubKey: key.getPubKeyHex(), + privKey: key.getBitcoinWalletImportFormat() } - -function convertStringToInt(string){ - return parseInt(string,10); + } catch (e) { + console.log(e); + } + }, + verifyPrivKey: function (privateKeyHex, floID) { + try { + var key = new Bitcoin.ECKey(privateKeyHex); + if (key.priv == null) + return false; + key.setCompressed(true); + if (floID == key.getBitcoinAddress()) + return true; + else + return false; + } catch (e) { + console.log(e); + } + } +} +//Script for AJAX, and register functions +function ajax(method, uri) { + var request = new XMLHttpRequest(); + var url = `${server}/${uri}` + console.log(url) + var result; + request.open(method, url, false); + request.onload = function () { + if (request.readyState == 4 && request.status == 200) + result = this.response; + else { + console.log('error'); + result = false; + } + }; + request.send(); + console.log(result); + return result; } -function userDataStartUp(){ - console.log("StartUp"); +function registerID(sender, onionAddr, wif, pubkey, username) { - document.getElementById("sendMsgInput").addEventListener("keyup",function(event){ - if(event.keyCode === 13){ - event.preventDefault(); - sendMsg(); - } - }); + var receiver = adminID; - getDatafromAPI().then(function (result) { - console.log(result); - getContactsfromIDB().then(function(result){ - contacts = arrayToObject(result); - console.log(contacts); - getSuperNodeListfromIDB().then(function(result){ + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + var x = sendAmt + fee; + var response = ajax("GET", `api/addr/${sender}/utxo`); + var utxos = JSON.parse(response); + for (var x = utxos.length - 1; x >= 0; x--) { + if (utxoAmt < sendAmt + fee) { + trx.addinput(utxos[x].txid, utxos[x].vout, utxos[x].scriptPubKey); + utxoAmt += utxos[x].amount; + } else + break; + } + console.log(utxoAmt + ":" + (sendAmt + fee)); + if (utxoAmt < sendAmt + fee) { + alert("Insufficient balance!"); + return; + } + + trx.addoutput(receiver, sendAmt); + console.log(receiver + ":" + sendAmt); + + var change = utxoAmt - sendAmt - fee; + if (change > 0) + trx.addoutput(sender, change); + console.log(sender + ":" + change); + var key = new Bitcoin.ECKey(wif); + var sendFloData = JSON.stringify({ + FLO_chat: { + onionAddr: onionAddr, + name: username, + pubKey: pubkey + } + });; + trx.addflodata(sendFloData); + console.log(sendFloData); + + var signedTxHash = trx.sign(wif, 1); + console.log(signedTxHash); + return broadcastTx(signedTxHash); +} + +function broadcastTx(signedTxHash) { + var http = new XMLHttpRequest(); + var url = `${server}/api/tx/send`; + if (signedTxHash.length < 1) { + alert("Empty Signature"); + return false; + } + + var params = `{"rawtx":"${signedTxHash}"}`; + var result; + http.open('POST', url, false); + + //Send the proper header information along with the request + http.setRequestHeader('Content-type', 'application/json'); + + http.onreadystatechange = () => { //Call a function when the state changes. + if (http.readyState == 4 && http.status == 200) { + console.log(http.response); + var txid = JSON.parse(http.response).txid.result; + alert("Transaction successful! txid : " + txid); + result = true; + + } else { + console.log(http.responseText); + result = false; + } + } + http.send(params); + return result; +} + +function resetForm(formID) { + var formEl = document.getElementById(formID); + formEl.reset() + var labelSpans = formEl.querySelectorAll('span'); + for (var i = 0; i < labelSpans.length; i++) + labelSpans[i].textContent = ''; +} + +function userDataStartUp() { + console.log("StartUp"); + resetForm("replyForm"); + //Initiate Event Handling + document.getElementById("msgInput").addEventListener("keydown", (event) => { + if (event.keyCode === 13 && !event.shiftKey) { + event.preventDefault(); + sendMsg(); + } + }); + document.getElementById("searchContact").addEventListener("input", searchContact, true); + document.getElementById("searchList").addEventListener("input", searchChecklist, true); + document.getElementById('fileInput').onchange = function () { + var fileName = this.value.split("\\").pop(); + this.nextSibling.textContent = fileName; + }; + + //Start Program + getDatafromAPI().then(result => { + console.log(result); + getContactsfromIDB().then(result => { + contacts = result; + getSuperNodeListfromIDB().then(result => { + console.log(result) + superNodeList = result; + kBucketObj.launchKBucket().then(result => { console.log(result) - superNodeList = result; - kBucketObj.launchKBucket().then(function(result){ - console.log(result) - getuserID().then(function(result){ - console.log(result); - selfID = result; - if(superNodeList.includes(selfID)) - modSuperNode = true; - alert(`${selfID}\nWelcome ${contacts[selfID].name}`) - readMsgfromIDB().then(function(result){ + getuserID().then(result => { + console.log(result); + selfID = result; + if (superNodeList.includes(selfID)) + modSuperNode = true; + alert(`${selfID}\nWelcome ${contacts[selfID].name}`) + getGroupsfromIDB().then(result => { + groups = result; + readMsgfromIDB().then(result => { console.log(result); initselfWebSocket(); - pingSuperNodeForMessages(); + pingSuperNodeForAwayMessages(); displayContacts(); const createClock = setInterval(checkStatusInterval, 30000); - }).catch(function(error){ + }).catch(error => { console.log(error); }); - //startChats(); - }).catch(function (error) { - console.log(error.message); + }).catch(error => { + console.log(error.message); }); - }).catch(function(error){ + }).catch(error => { console.log(error.message); - }); - }).catch(function (error) { + }); + }).catch(error => { console.log(error.message); }); - }).catch(function (error) { + }).catch(error => { console.log(error.message); }); - }).catch(function (error) { - console.log(error.message); + }).catch(error => { + console.log(error.message); }); + }).catch(error => { + console.log(error.message); + }); } - function arrayToObject(array){ - obj = {}; - array.forEach(element => { - obj[element.floID] = {onionAddr : element.onionAddr, name : element.name, pubKey : element.pubKey}; + +function storeContact(data) { + return new Promise((resolve, reject) => { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction('contacts', "readwrite").objectStore('contacts'); + objectRequest = obs.put(data); + objectRequest.onerror = (event) => { + reject(Error('Error occured: Unable to store data')); + }; + + objectRequest.onsuccess = (event) => { + resolve('Data saved OK'); + db.close(); + }; + }; + }); +} + +function storeSuperNodeData(data) { + return new Promise((resolve, reject) => { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction('superNodes', "readwrite").objectStore('superNodes'); + if (data.addNodes) + for (var i = 0; i < data.addNodes.length; i++) + obs.add(true, data.addNodes[i]) + if (data.removeNodes) + for (var i = 0; i < data.removeNodes.length; i++) + obs.delete(data.removeNodes[i]) + db.close(); + resolve('Updated superNodes list in IDB'); + }; + }); +} + +function getDatafromAPI() { + return new Promise((resolve, reject) => { + var addr = adminID; + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + console.log("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = (event) => { + var db = event.target.result; + var objectStore0 = db.createObjectStore("superNodes"); + var objectStore1 = db.createObjectStore("contacts", { + keyPath: 'floID' }); - return obj; - } - - function storeContact(data){ - return new Promise( - function(resolve, reject) { - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - console.log("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - var obs = db.transaction('contacts', "readwrite").objectStore('contacts'); - objectRequest = obs.put(data); - objectRequest.onerror = function(event) { - reject(Error('Error occured: Unable to store data')); - }; - - objectRequest.onsuccess = function(event) { - resolve('Data saved OK'); - db.close(); - }; - }; - } - ); - } - - function storeSuperNodeData(data){ - return new Promise( - function(resolve, reject) { - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - reject("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - var obs = db.transaction('superNodes', "readwrite").objectStore('superNodes'); - if(data.addNodes) - for(var i=0; i { + var db = event.target.result; + var lastTx = db.transaction('lastTx', "readwrite").objectStore('lastTx'); + console.log(addr); + new Promise((res, rej) => { + var lastTxReq = lastTx.get(addr); + lastTxReq.onsuccess = (event) => { + var lasttx = event.target.result; + if (lasttx === undefined) { + lasttx = 0; } - ); - } - - function getDatafromAPI(){ - return new Promise( - function(resolve, reject) { - var addr = adminID; - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - console.log("Error in opening IndexedDB!"); - }; - idb.onupgradeneeded = function(event) { - var objectStore0 = event.target.result.createObjectStore("superNodes"); - var objectStore1 = event.target.result.createObjectStore("contacts",{ keyPath: 'floID' }); - objectStore1.createIndex('onionAddr', 'onionAddr', { unique: false }); - objectStore1.createIndex('name', 'name', { unique: false }); - objectStore1.createIndex('pubKey', 'pubKey', { unique: false }); - var objectStore2 = event.target.result.createObjectStore("lastTx"); - var objectStore3 = event.target.result.createObjectStore("messages",{ keyPath: 'time' }); - objectStore3.createIndex('text', 'text', { unique: false }); - objectStore3.createIndex('floID', 'floID', { unique: false }); - objectStore3.createIndex('type', 'type', { unique: false }); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - //window["wait"] = addrList.length; - var lastTx = db.transaction('lastTx', "readwrite").objectStore('lastTx'); - //addrList.forEach(function(addr){ - console.log(addr); - new Promise(function(res,rej){ - var lastTxReq = lastTx.get(addr); - lastTxReq.onsuccess = function(event){ - var lasttx = event.target.result; - if(lasttx === undefined){ - lasttx = 0; - } - res(lasttx); - } - }).then(function(lasttx){ - var response = ajax("GET",`api/addrs/${addr}/txs`); - var nRequired = JSON.parse(response).totalItems - lasttx; - console.log(nRequired); - while(true && nRequired){ - var response = ajax("GET",`api/addrs/${addr}/txs?from=0&to=${nRequired}`); - response = JSON.parse(response); - if (nRequired + lasttx != response.totalItems ){ - nRequired = response.totalItems - lasttx; - continue; - } - response.items.reverse().forEach(function(tx){ - try { - if (tx.vin[0].addr == addr){ - var data = JSON.parse(tx.floData).FLO_chat_SuperNode; - if(data !== undefined){ - storeSuperNodeData(data).then(function (response) { - }).catch(function (error) { - console.log(error.message); - }); - } - }else{ - var data = JSON.parse(tx.floData).FLO_chat; - if(data !== undefined){ - if(encrypt.getFLOIDfromPubkeyHex(data.pubKey)!=tx.vin[0].addr) - throw("PublicKey doesnot match with floID") - data = {floID : tx.vin[0].addr, onionAddr : data.onionAddr, name : data.name, pubKey:data.pubKey}; - storeContact(data).then(function (response) { - }).catch(function (error) { - console.log(error.message); - }); - } - } - } catch (e) { - console.log(e) - } - }); - - var obs = db.transaction('lastTx', "readwrite").objectStore('lastTx'); - obs.put(response.totalItems,addr); - break; - } - window["wait"]--; - db.close(); - resolve('retrived data from API'); - }); - }; + res(lasttx); + } + }).then(lasttx => { + var response = ajax("GET", `api/addrs/${addr}/txs`); + var nRequired = JSON.parse(response).totalItems - lasttx; + console.log(nRequired); + while (true && nRequired) { + var response = ajax("GET", `api/addrs/${addr}/txs?from=0&to=${nRequired}`); + response = JSON.parse(response); + if (nRequired + lasttx != response.totalItems) { + nRequired = response.totalItems - lasttx; + continue; + } + response.items.reverse().forEach(tx => { + try { + if (tx.vin[0].addr == addr) { + var data = JSON.parse(tx.floData).FLO_chat_SuperNode; + if (data !== undefined) { + storeSuperNodeData(data).then(response => {}).catch(error => { + console.log(error.message); + }); + } + } else { + var data = JSON.parse(tx.floData).FLO_chat; + if (data !== undefined) { + if (floOpt.getFLOIDfromPubkeyHex(data.pubKey) != tx.vin[0].addr) + throw ("PublicKey doesnot match with floID") + data = { + floID: tx.vin[0].addr, + onionAddr: data.onionAddr, + name: data.name, + pubKey: data.pubKey + }; + storeContact(data).then(response => {}).catch(error => { + console.log(error.message); + }); + } } - ); - } - -function getuserID(){ - return new Promise( - function(resolve,reject){ - privKey = prompt("Enter FLO Private Key : ") - var key = new Bitcoin.ECKey(privKey); - while(key.priv == null){ - privKey = prompt("Invalid FLO Private Key! Retry : ") - key = Bitcoin.ECKey(privKey); - } - key.setCompressed(true); - var userID = key.getBitcoinAddress(); - if (contacts[userID] === undefined) - var reg = confirm(`${userID} is not registers to FLO chat!\nRegister FLO ID to this onion?`); - else if (contacts[userID].onionAddr == window.location.host) - resolve(userID) - else - var reg = confirm(`${userID} is registered to another onion!\nChange to this onion?`); - - if(reg){ - var name = prompt("Enter your name :"); - var pubKey = key.getPubKeyHex(); - if(registerID(userID,window.location.host,privKey,pubKey,name)){ - contacts[userID] = {onionAddr : window.location.host, name : name, pubKey : pubKey}; - resolve(userID); - } - } - reject(`Unable to bind ${userID} to this onionAddress!\nTry again later!`); - } - ); -} - -function getContactsfromIDB(){ - return new Promise( - function(resolve,reject){ - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - reject("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - var obs = db.transaction("contacts", "readwrite").objectStore("contacts"); - var getReq = obs.getAll(); - getReq.onsuccess = function(event){ - resolve(event.target.result); - } - getReq.onerror = function(event){ - reject('Unable to read contacts!') - } - db.close(); - }; - } - ); -} - -function getSuperNodeListfromIDB(){ - return new Promise( - function(resolve,reject){ - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - reject("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - var obs = db.transaction("superNodes", "readwrite").objectStore("superNodes"); - var getReq = obs.getAllKeys(); - getReq.onsuccess = function(event){ - resolve(event.target.result); - } - getReq.onerror = function(event){ - reject('Unable to read superNode list!') - } - db.close(); - }; - } - ); -} - -function readMsgfromIDB(){ - return new Promise( - function(resolve,reject){ - var disp = document.getElementById("conversation"); - for(floID in contacts){ - var createLi = document.createElement('div'); - createLi.setAttribute("id", floID); - createLi.setAttribute("class", "message-inner"); - createLi.style.display = 'none'; - disp.appendChild(createLi); - } - var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { - reject("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - var obs = db.transaction("messages", "readwrite").objectStore("messages"); - obs.openCursor().onsuccess = function(event) { - var cursor = event.target.result; - if(cursor) { - var chat = document.getElementById(cursor.value.floID); - if(cursor.value.type == "R"){ - var msgdiv = document.createElement('div'); - msgdiv.setAttribute("class", "row message-body"); - msgdiv.innerHTML = `
-
- - ${cursor.value.text} - - - ${getTime(cursor.value.time)} - -
-
`; - chat.appendChild(msgdiv); - }else if(cursor.value.type == "S"){ - var msgdiv = document.createElement('div'); - msgdiv.setAttribute("class", "row message-body"); - msgdiv.innerHTML = `
-
- ${cursor.value.text} - - - ${getTime(cursor.value.time)} - -
-
`; - chat.appendChild(msgdiv); + } catch (e) { + console.log(e) } + }); - cursor.continue(); - } else { - console.log('Entries all displayed.'); - resolve("Read Msg from IDB"); - } - }; + var obs = db.transaction('lastTx', "readwrite").objectStore('lastTx'); + obs.put(response.totalItems, addr); + break; + } db.close(); - }; - } - ); + resolve('retrived data from API'); + }); + }; + }); } -function storeMsg(data){ +function getuserID() { + return new Promise((resolve, reject) => { + privKey = prompt("Enter FLO Private Key : ") + var key = new Bitcoin.ECKey(privKey); + while (key.priv == null) { + privKey = prompt("Invalid FLO Private Key! Retry : ") + key = Bitcoin.ECKey(privKey); + } + key.setCompressed(true); + var userID = key.getBitcoinAddress(); + if (contacts[userID] === undefined) + var reg = confirm(`${userID} is not registers to FLO chat!\nRegister FLO ID to this onion?`); + else if (contacts[userID].onionAddr == window.location.host) + resolve(userID) + else + var reg = confirm(`${userID} is registered to another onion!\nChange to this onion?`); + + if (reg) { + var name = prompt("Enter your name :"); + var pubKey = key.getPubKeyHex(); + if (registerID(userID, window.location.host, privKey, pubKey, name)) { + contacts[userID] = { + onionAddr: window.location.host, + name: name, + pubKey: pubKey + }; + resolve(userID); + } + } + reject(`Unable to bind ${userID} to this onionAddress!\nTry again later!`); + }); +} + +function getContactsfromIDB() { + return new Promise((resolve, reject) => { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction("contacts", "readwrite").objectStore("contacts"); + var getReq = obs.getAll(); + getReq.onsuccess = (event) => { + var result = {} + event.target.result.forEach(c => { + result[c.floID] = c; + searchIndex.add(c.floID, c.name + ' @' + c.floID); + }); + resolve(result); + } + getReq.onerror = (event) => { + reject('Unable to read contacts!') + } + db.close(); + }; + }); +} + +function getGroupsfromIDB() { + return new Promise((resolve, reject) => { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction("groups", "readwrite").objectStore("groups"); + var getReq = obs.getAll(); + getReq.onsuccess = (event) => { + var result = {} + event.target.result.forEach(g => { + var gInfo = JSON.parse(g.groupInfo); + result[g.groupID] = gInfo; + searchIndex.add(g.groupID, gInfo.name + ' #' + gInfo.floID); + }); + resolve(result); + } + getReq.onerror = (event) => { + reject('Unable to read groups!') + } + db.close(); + }; + }); +} + +function getSuperNodeListfromIDB() { + return new Promise((resolve, reject) => { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction("superNodes", "readwrite").objectStore("superNodes"); + var getReq = obs.getAllKeys(); + getReq.onsuccess = (event) => { + resolve(event.target.result); + } + getReq.onerror = (event) => { + reject('Unable to read superNode list!') + } + db.close(); + }; + }); +} + +function readMsgfromIDB() { + return new Promise((resolve, reject) => { + var disp = document.getElementById("conversation"); + for (floID in contacts) { + var createLi = document.createElement('div'); + createLi.setAttribute("id", floID); + createLi.setAttribute("class", "message-inner"); + createLi.style.display = 'none'; + disp.appendChild(createLi); + } + for (floID in groups) { + var createLi = document.createElement('div'); + createLi.setAttribute("id", floID); + createLi.setAttribute("class", "message-inner"); + createLi.style.display = 'none'; + disp.appendChild(createLi); + } + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var obs = db.transaction("messages", "readwrite").objectStore("messages"); + obs.openCursor().onsuccess = (event) => { + var cursor = event.target.result; + if (cursor) { + createMsgElement(cursor.value); + cursor.continue(); + } else { + resolve("Read Msg from IDB"); + } + }; + db.close(); + }; + }); +} + +function storeMsg(data) { var idb = indexedDB.open("FLO_Chat"); - idb.onerror = function(event) { + idb.onerror = (event) => { console.log("Error in opening IndexedDB!"); }; - idb.onsuccess = function(event) { + idb.onsuccess = (event) => { var db = event.target.result; var obs = db.transaction("messages", "readwrite").objectStore("messages"); obs.add(data); @@ -556,202 +713,366 @@ function storeMsg(data){ }; } -function storeSuperNodeMsg(data){ - var idb = indexedDB.open("FLO_Chat",2); - idb.onerror = function(event) { +function storeSuperNodeMsg(data) { + var idb = indexedDB.open("FLO_Chat", 2); + idb.onerror = (event) => { console.log("Error in opening IndexedDB!"); }; - idb.onupgradeneeded = function(event) { - var objectStore = event.target.result.createObjectStore("superNodeMsg",{ keyPath: 'id' }); - objectStore.createIndex('from', 'from', { unique: false }); - objectStore.createIndex('to', 'to', { unique: false }); - objectStore.createIndex('data', 'data', { unique: false }); + idb.onupgradeneeded = (event) => { + var objectStore = event.target.result.createObjectStore("superNodeMsg", { + keyPath: 'id' + }); + objectStore.createIndex('from', 'from', { + unique: false + }); + objectStore.createIndex('to', 'to', { + unique: false + }); + objectStore.createIndex('data', 'data', { + unique: false + }); }; - idb.onsuccess = function(event) { + idb.onsuccess = (event) => { var db = event.target.result; var obs = db.transaction("superNodeMsg", "readwrite").objectStore("superNodeMsg"); var parsedData = JSON.parse(data); - var id = ''+parsedData.from+'_'+parsedData.to+'_'+parsedData.time; - obs.add({id:id,from:parsedData.from,to:parsedData.to,data:data}); + var id = '' + parsedData.from + '_' + parsedData.to + '_' + parsedData.time; + obs.add({ + id: id, + from: parsedData.from, + to: parsedData.to, + data: data + }); db.close(); }; } -function displayContacts(){ +function displayContacts() { console.log('displayContacts'); var listElement = document.getElementById('contact-display'); - for(floID in contacts){ + for (floID in contacts) { var createLi = document.createElement('div'); createLi.setAttribute("name", floID); createLi.setAttribute("onClick", 'changeReceiver(this)'); createLi.setAttribute("class", "row sideBar-body"); createLi.innerHTML = ``; - disp.appendChild(msgdiv); - storeMsg({time:time,floID:receiverID,text:msg,type:"S"}); + getReplyInputs().then(msgData => { + console.log(msgData); + var time = Date.now(); + var sign = floOpt.signData(msgData, privKey); + if (msgType === 'direct') + sendDirectMsg(msgData, time, sign); + else if (msgType === 'group') + sendGroupMsg(msgData, time, sign); + }).catch(error => { + console.log(error); + }) } -function sendStoredSuperNodeMsgs(floID){ - var receiverWS = new WebSocket("ws://"+contacts[floID].onionAddr+"/ws"); - receiverWS.onopen = function(ev){ - var idb = indexedDB.open("FLO_Chat",2); - idb.onerror = function(event) { +function getfileData(fileInput) { + return new Promise((resolve, reject) => { + try { + var files = document.getElementById(fileInput).files; + if (files.length == 0) + resolve(null); + else { + var reader = new FileReader(); + reader.onload = (event) => { + var fileBytes = Crypto.charenc.Binary.stringToBytes(event.target.result); + resolve({ + name: files[0].name, + size: files[0].size, + content: Crypto.util.bytesToBase64(fileBytes) + }); + }; + reader.onerror = (event) => { + reject("File could not be read! Code " + event.target.error.code); + }; + reader.readAsBinaryString(files[0]); + } + } catch (e) { + reject(e); + } + }); +} + +function getReplyInputs() { + return new Promise((resolve, reject) => { + getfileData('fileInput').then(fileData => { + var msgData = { + text: document.getElementById('msgInput').value, + file: fileData + }; + resetForm('replyForm'); + resolve(JSON.stringify(msgData)); + }).catch(error => { + reject(error); + }); + }); +} + +function sendDirectMsg(msgData, time, sign) { + var data = JSON.stringify({ + from: selfID, + to: receiverID, + directMsg: { + time: time, + msgCipher: floOpt.encryptData(msgData, contacts[receiverID].pubKey), + sign: sign + } + }); + if (recStat) + receiverWebSocket.send(data); + else + sendDataToSuperNode(receiverID, data); + + var msgInfo = { + time: time, + floID: receiverID, + msgData: msgData, + type: "S" + } + createMsgElement(msgInfo); + storeMsg(msgInfo); +} + +function sendGroupMsg(msgData, time, sign) { + var data = { + from: selfID, + groupMsg: { + group: receiverID, + time: time, + msgCipher: floOpt.encryptData(msgData, groups[receiverID].pubKey), + sign: sign + } + }; + console.log(data); + + groups[receiverID].members.forEach(floID => { + if (floID == selfID) //dont send to self + return; + data.to = floID; + sendData(floID, JSON.stringify(data)); + }); + var msgInfo = { + time: time, + sender: selfID, + groupID: receiverID, + msgData: msgData, + type: "S" + } + createMsgElement(msgInfo); + storeMsg(msgInfo); +} + +function sendStoredSuperNodeMsgs(floID) { + var receiverWS = new WebSocket("ws://" + contacts[floID].onionAddr + "/ws"); + receiverWS.onopen = (ev) => { + var idb = indexedDB.open("FLO_Chat", 2); + idb.onerror = (event) => { console.log("Error in opening IndexedDB!"); }; - idb.onupgradeneeded = function(event) { - var objectStore = event.target.result.createObjectStore("superNodeMsg",{ keyPath: 'id' }); - objectStore.createIndex('from', 'from', { unique: false }); - objectStore.createIndex('to', 'to', { unique: false }); - objectStore.createIndex('data', 'data', { unique: false }); + idb.onupgradeneeded = (event) => { + var objectStore = event.target.result.createObjectStore("superNodeMsg", { + keyPath: 'id' + }); + objectStore.createIndex('from', 'from', { + unique: false + }); + objectStore.createIndex('to', 'to', { + unique: false + }); + objectStore.createIndex('data', 'data', { + unique: false + }); }; - idb.onsuccess = function(event) { + idb.onsuccess = (event) => { var db = event.target.result; var obs = db.transaction("superNodeMsg", "readwrite").objectStore("superNodeMsg"); - obs.openCursor().onsuccess = function(event) { + obs.openCursor().onsuccess = (event) => { var cursor = event.target.result; - if(cursor) { - if(cursor.value.to == floID){ + if (cursor) { + if (cursor.value.to == floID) { receiverWS.send(cursor.value.data); cursor.delete(); } cursor.continue(); - }else{ - console.log('Sent All messages to '+floID) + } else { + console.log('Sent All messages to ' + floID) } } db.close(); }; }; - receiverWS.onerror = function(ev) { console.log('Connection Error to '+floID) }; - receiverWS.onclose = function(ev) { console.log('Disconnected from '+floID) }; + receiverWS.onerror = (ev) => { + console.log('Connection Error to ' + floID) + }; + receiverWS.onclose = (ev) => { + console.log('Disconnected from ' + floID) + }; +} + +async function sendData(floID, data) { + try { + var recipientWS = new WebSocket("ws://" + contacts[floID].onionAddr + "/ws"); + recipientWS.onopen = (ev) => { + recipientWS.send('#'); + }; + recipientWS.onerror = (ev) => { + sendDataToSuperNode(floID, data); + }; + recipientWS.onclose = (ev) => { + console.log("Closed") + }; + recipientWS.onmessage = (evt) => { + console.log(evt.data); + if (evt.data[0] == '#') { + if (evt.data[1] == '+') + recipientWS.send(data); + else if (evt.data[1] == '-') + sendDataToSuperNode(floID, data); + } + } + } catch (e) { + console.log(e); + } +} + +function sendDataToSuperNode(floID, data) { + kBucketObj.determineClosestSupernode(floID).then(result => { + var superNodeWS = new WebSocket("ws://" + contacts[result[0].floID].onionAddr + "/ws"); + superNodeWS.onopen = (ev) => { + console.log(`Connected to ${floID}'s SuperNode!`); + superNodeWS.send(data); + }; + superNodeWS.onerror = (ev) => { + console.log(`${floID}'s SuperNode is offline!`); + }; + superNodeWS.onclose = (ev) => { + console.log(`Disconnected from ${floID}'s SuperNode!`); + }; + }).catch(e => { + console.log(e.message); + }); +} + +function createGroupDisplay(groupInfo) { + var createLi = document.createElement('div'); + createLi.setAttribute("name", groupInfo.floID); + createLi.setAttribute("onClick", 'changeReceiver(this)'); + createLi.setAttribute("class", "row sideBar-body"); + createLi.innerHTML = ``; + createLi.querySelector("span.name-meta").textContent = groupInfo.name; + document.getElementById('contact-display').appendChild(createLi); + + var createEl = document.createElement('div'); + createEl.setAttribute("id", groupInfo.floID); + createEl.setAttribute("class", "message-inner"); + createEl.style.display = 'none'; + document.getElementById("conversation").appendChild(createEl); +} + +function storeGroup(groupInfoStr, groupID) { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + console.log(groupID, groupInfoStr); + var obs = db.transaction('groups', "readwrite").objectStore('groups'); + obs.put({ + groupID: groupID, + groupInfo: groupInfoStr + }); + db.close(); + }; +} + +function deleteGroupFromIDB(groupID) { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + console.log('Delete Group:', groupID); + var obs = db.transaction('groups', "readwrite").objectStore('groups'); + obs.delete(groupID); + db.close(); + }; +} + +function createGroup() { + customCheckList(Object.keys(contacts), [selfID], 'Create Group', 'success').then(result => { + var grpInfo = floOpt.genNewIDpair(); + grpInfo.name = result.grpName; + grpInfo.members = result.members; + grpInfo.members.push(selfID) + grpInfo.creator = selfID; + grpInfo.admins = []; + var grpInfoStr = JSON.stringify(grpInfo); + console.log(grpInfoStr); + var data = { + from: selfID, + newGroup: { + sign: floOpt.signData(grpInfoStr, privKey) + } + } + grpInfo.members.forEach(floID => { + data.to = floID; + data.newGroup.groupInfo = floOpt.encryptData(grpInfoStr, contacts[floID].pubKey), + sendData(floID, JSON.stringify(data)); + }); + }).catch(error => { + console.log(error); + }) +} + +function deleteGroup() { + var flag = confirm("Are you sure you want to delete this group?"); + if (flag) { + var data = { + from: selfID, + deleteGroup: { + group: receiverID, + sign: floOpt.signData('deleteGroup:' + receiverID, privKey) + } + }; + groups[receiverID].members.forEach(floID => { + data.to = floID; + sendData(floID, JSON.stringify(data)); + }); + } +} + +function addGroupMembers() { + customCheckList(Object.keys(contacts), groups[receiverID].members, 'Add Members', 'success').then(result => { + var newMembers = result.members; + var data1 = { + from: selfID, + addGroupMembers: { + group: receiverID, + members: newMembers, + sign: floOpt.signData('addGroupMembers:' + receiverID + newMembers.join('|'), privKey) + } + } + groups[receiverID].members.forEach(floID => { + if (floID == selfID) //dont send to self + return; + data1.to = floID; + sendData(floID, JSON.stringify(data1)); + }); + groups[receiverID].members = groups[receiverID].members.concat(newMembers); + var grpInfoStr = JSON.stringify(groups[receiverID]); + console.log(grpInfoStr); + var data2 = { + from: selfID, + newGroup: { + sign: floOpt.signData(grpInfoStr, privKey) + } + } + newMembers.forEach(floID => { + data2.to = floID; + data2.newGroup.groupInfo = floOpt.encryptData(grpInfoStr, contacts[floID].pubKey), + sendData(floID, JSON.stringify(data2)); + }); + storeGroup(grpInfoStr, receiverID); + }).catch(error => { + console.log(error); + }) +} + +function rmGroupMembers() { + customCheckList(groups[receiverID].members, [], 'Remove Members', 'danger').then(result => { + var rmMembers = result.members; + var data1 = { + from: selfID, + rmGroupMembers: { + group: receiverID, + members: rmMembers, + sign: floOpt.signData('rmGroupMembers:' + receiverID + rmMembers.join('|'), privKey) + } + } + groups[receiverID].members = groups[receiverID].members.filter(x => !rmMembers.includes(x)); //remove member from group + storeGroup(JSON.stringify(groups[receiverID]), receiverID); + groups[receiverID].members.forEach(floID => { + if (floID == selfID) + return; + data1.to = floID; + sendData(floID, JSON.stringify(data1)); + }); + var data2 = { + from: selfID, + deleteGroup: { + group: receiverID, + sign: floOpt.signData('deleteGroup:' + receiverID, privKey) + } + }; + rmMembers.forEach(floID => { + data2.to = floID; + sendData(floID, JSON.stringify(data2)); + }); + }).catch(error => { + console.log(error); + }) +} + +function addGroupAdmins() { + customCheckList(groups[receiverID].members, groups[receiverID].admins, 'Add Admins', 'success').then(result => { + var newAdmins = result.members; + var data = { + from: selfID, + addGroupAdmins: { + group: receiverID, + admins: newAdmins, + sign: floOpt.signData('addGroupAdmins:' + receiverID + newAdmins.join('|'), privKey) + } + } + groups[receiverID].members.forEach(floID => { + if (floID == selfID) //dont send to self + return; + data.to = floID; + sendData(floID, JSON.stringify(data)); + }); + groups[receiverID].admins = groups[receiverID].admins.concat(newAdmins); + var grpInfoStr = JSON.stringify(groups[receiverID]); + storeGroup(grpInfoStr, receiverID); + }).catch(error => { + console.log(error); + }) +} + +function rmGroupAdmins() { + customCheckList(groups[receiverID].admins, [], 'Remove Admins', 'danger').then(result => { + var rmAdmins = result.members; + var data = { + from: selfID, + rmGroupAdmins: { + group: receiverID, + admins: rmAdmins, + sign: floOpt.signData('rmGroupAdmins:' + receiverID + rmAdmins.join('|'), privKey) + } + } + groups[receiverID].members.forEach(floID => { + if (floID == selfID) //dont send to self + return; + data.to = floID; + sendData(floID, JSON.stringify(data)); + }); + groups[receiverID].admins = groups[receiverID].admins.filter(x => !rmAdmins.includes(x)); //remove admins + var grpInfoStr = JSON.stringify(groups[receiverID]); + storeGroup(grpInfoStr, receiverID); + }).catch(error => { + console.log(error); + }) +} + +function searchContact() { + try { + var searchKey = this.value; + if (!searchKey) + var searchResults = Object.keys(contacts).concat(Object.keys(groups)); + else + var searchResults = searchIndex.search(searchKey); + var contactList = document.getElementById('contact-display').children; + for (var i = 0; i < contactList.length; i++) { + if (searchResults.includes(contactList[i].getAttribute("name"))) + contactList[i].style.display = 'block'; + else + contactList[i].style.display = 'none'; + }; + } catch (e) { + console.log(e); + } +} + +function customCheckList(userList, ignoreList, okBtnVal = "Ok", okBtnType = "success") { + var dialog = document.getElementById('overlay'); + dialog.style.display = "block"; + var okButton = dialog.querySelector('button.ok'); + var cancelButton = dialog.querySelector('button.cancel'); + okButton.setAttribute("class", `ok btn btn-${okBtnType}`); + okButton.textContent = okBtnVal; + var grpNameInput = dialog.querySelector('input.grpName') + grpNameInput.style.display = (okBtnVal === "Create Group" ? "block" : "none"); + grpNameInput.value = ''; + var userChecklist = document.getElementById('userChecklist'); + userChecklist.innerHTML = ''; + for (var i = 0; i < userList.length; i++) { + if (ignoreList.includes(userList[i])) + continue; + var listEl = document.createElement('label'); + listEl.setAttribute('class', "btn btn-default listLabel"); + listEl.setAttribute('name', userList[i]); + listEl.innerHTML = ` +
+ @${userList[i]} + + `; + listEl.querySelector("span").textContent = contacts[userList[i]].name; + userChecklist.appendChild(listEl); + } + return new Promise((resolve, reject) => { + dialog.addEventListener('click', function handleButtonClicks(e) { + if (e.target.tagName !== 'BUTTON') { + return; + } + dialog.removeEventListener('click', handleButtonClicks); + dialog.style.display = 'none'; + if (e.target === okButton) { + var selectedList = []; + var checklist = dialog.querySelectorAll('input.badgebox'); + for (var i = 0; i < checklist.length; i++) + if (checklist[i].checked) + selectedList.push(checklist[i].value); + if (selectedList.length == 0) + reject('User Didnt select Any Users!'); + else + resolve({ + grpName: grpNameInput.value, + members: selectedList + }); + } else if (e.target === cancelButton) { + reject('User cancelled!'); + } else { + reject('Some other button was clicked!'); + } + }); + }); +} + +function searchChecklist() { + try { + var searchKey = this.value; + if (!searchKey) + var searchResults = Object.keys(contacts); + else + var searchResults = searchIndex.search(searchKey); + var checklist = document.getElementById('userChecklist').children; + for (var i = 0; i < checklist.length; i++) { + if (searchResults.includes(checklist[i].getAttribute("name"))) + checklist[i].style.display = 'block'; + else + checklist[i].style.display = 'none'; + }; + } catch (e) { + console.log(e); + } +} + +function downloadFile(msgID) { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = (event) => { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = (event) => { + var db = event.target.result; + var msgReq = db.transaction('messages', "readwrite").objectStore('messages').get(msgID); + msgReq.onsuccess = (event) => { + var file = JSON.parse(event.target.result.msgData).file; + var tmpEl = document.createElement('a'); + tmpEl.setAttribute('href', 'data:application/octet-stream;charset=utf-8;base64,' + file.content); + tmpEl.setAttribute('download', file.name); + tmpEl.style.display = 'none'; + document.body.appendChild(tmpEl); + tmpEl.click(); + document.body.removeChild(tmpEl); + } + } } \ No newline at end of file diff --git a/app/index.html b/app/index.html index d02fb3a..92b984e 100644 --- a/app/index.html +++ b/app/index.html @@ -9,13 +9,30 @@ - + - + + +
+
+ + +
+ + +
+
@@ -24,34 +41,35 @@
-
FLO Whatsapp
created by Ranchimall
+ FLO Whatsapp +
+ +
+
+ created by Ranchimall
+ - - +
@@ -63,14 +81,20 @@
- O   Select Contact + O    + Select Contact
-
@@ -81,22 +105,32 @@
- -
- -
- -
- -
+
+ + +
+ +
+
+ +
+ +
+ +
+
diff --git a/app/registerID.js b/app/init.js similarity index 90% rename from app/registerID.js rename to app/init.js index 62f5e91..7d6d3ac 100755 --- a/app/registerID.js +++ b/app/init.js @@ -1,10 +1,10 @@ -const crypto = "FLO" +const cryptocoin = "FLO" const mainnet = `https://flosight.duckdns.org`; const testnet = `https://testnet-flosight.duckdns.org`; -if(crypto == "FLO") +if(cryptocoin == "FLO") var server = mainnet; -else if(crypto == "FLO_TEST") +else if(cryptocoin == "FLO_TEST") var server = testnet; const adminID = "FRR3Zz5Nod6oZTHE18seMpMYLzuLGuBWz4"; @@ -5086,9 +5086,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ); //https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js Bitcoin.Address = function (bytes) { - if(crypto == "FLO") + if(cryptocoin == "FLO") this.version = 0x23; // FLO mainnet public address - else if(crypto == "FLO_TEST") + else if(cryptocoin == "FLO_TEST") this.version = 0x73; // FLO testnet public address if ("string" == typeof bytes) { bytes = Bitcoin.Address.decodeString(bytes,this.version); @@ -5524,8 +5524,8 @@ Bitcoin.ECKey = (function () { try{ // This part is edited for FLO. FLO WIF are always compressed WIF. FLO WIF (private key) starts with R for mainnet and c for testnet. - if(((crypto == "FLO") && /^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input)) || - ((crypto == "FLO_TEST") && /^c[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input))) { + if(((cryptocoin == "FLO") && /^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input)) || + ((cryptocoin == "FLO_TEST") && /^c[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input))) { bytes = ECKey.decodeCompressedWalletImportFormat(input); this.compressed = true; }else if (ECKey.isHexFormat(input)) { @@ -5579,9 +5579,9 @@ Bitcoin.ECKey = (function () { } }; - if(crypto == "FLO") + if(cryptocoin == "FLO") ECKey.privateKeyPrefix = 0xA3; //(Bitcoin mainnet 0x80 testnet 0xEF) (FLO mainnet 0xA3 163 D) - else if(crypto == "FLO_TEST") + else if(cryptocoin == "FLO_TEST") ECKey.privateKeyPrefix = 0xEF; //FLO testnet /** @@ -6078,125 +6078,46 @@ Bitcoin.Util = { })("secp256k1"); - -//Script for AJAX, and register functions -function ajax(method, uri){ - var request = new XMLHttpRequest(); - var url = `${server}/${uri}` - console.log(url) - var result; - request.open(method,url , false); - request.onload = function () { - if (request.readyState == 4 && request.status == 200) - result = this.response; - else { - console.log('error'); - result = false; - } - }; - request.send(); - console.log(result); - return result; - } - - function validateAddr(inputtxt) { - try{ - var addr = new Bitcoin.Address(inputtxt); - return true; - - }catch{ - return false; - } - } - - function verifyWIF(wif,addr){ - try { - var key = new Bitcoin.ECKey(wif); - if(key.priv == null){ - return false; - } - key.setCompressed(true); - var bitcoinAddress = key.getBitcoinAddress(); - if (addr == bitcoinAddress) - return true; - else - return false; - } - catch (e) { - // browser does not have sufficient JavaScript support to generate a bitcoin address - alert(e); - console.log("error"); - } - } - - - -function registerID(sender,onionAddr,wif,pubkey,username) { - - var receiver = adminID; - - var trx = bitjs.transaction(); - var utxoAmt = 0.0; - var x = sendAmt+fee; - var response = ajax("GET",`api/addr/${sender}/utxo`); - var utxos = JSON.parse(response); - for(var x = utxos.length-1; x >= 0; x--){ - if(utxoAmt < sendAmt+fee){ - trx.addinput(utxos[x].txid, utxos[x].vout, utxos[x].scriptPubKey); - utxoAmt += utxos[x].amount; - }else - break; - } - console.log(utxoAmt+":"+(sendAmt+fee)); - if(utxoAmt < sendAmt+fee){ - alert("Insufficient balance!"); - return; - } - - trx.addoutput(receiver, sendAmt); - console.log(receiver+":"+ sendAmt); - - var change = utxoAmt-sendAmt-fee; - if(change>0) - trx.addoutput(sender, change); - console.log(sender+":"+ change); - var key = new Bitcoin.ECKey(wif); - var sendFloData = JSON.stringify({FLO_chat:{onionAddr:onionAddr, name: username, pubKey: pubkey}});; - trx.addflodata(sendFloData); - console.log(sendFloData); - - var signedTxHash = trx.sign(wif, 1); - console.log(signedTxHash); - return broadcastTx(signedTxHash); - } - function broadcastTx(signedTxHash) { - var http = new XMLHttpRequest(); - var url = `${server}/api/tx/send`; - if (signedTxHash.length < 1) { - alert("Empty Signature"); - return false; - } - - var params = `{"rawtx":"${signedTxHash}"}`; - var result; - http.open('POST', url, false); - - //Send the proper header information along with the request - http.setRequestHeader('Content-type', 'application/json'); - - http.onreadystatechange = function () { //Call a function when the state changes. - if (http.readyState == 4 && http.status == 200) { - console.log(http.response); - var txid = JSON.parse(http.response).txid.result; - alert("Transaction successful! txid : " + txid); - result = true; - - } else { - console.log(http.responseText); - result = false; - } - } - http.send(params); - return result; - } - +/* + FlexSearch v0.6.22 + Copyright 2019 Nextapps GmbH + Author: Thomas Wilkerling + Released under the Apache 2.0 Licence + https://github.com/nextapps-de/flexsearch + */ + //FlexSearch min mode + DEBUG = true; + 'use strict';(function(I,R,v){let K;(K=v.define)&&K.amd?K([],function(){return R}):(K=v.modules)?K[I.toLowerCase()]=R:"object"===typeof exports?module.exports=R:v[I]=R})("FlexSearch",function ma(I){function v(a,b){const c=b?b.id:a&&a.id;this.id=c||0===c?c:na++;this.init(a,b);fa(this,"index",function(){return this.a?Object.keys(this.a.index[this.a.keys[0]].c):Object.keys(this.c)});fa(this,"length",function(){return this.index.length})}function K(a,b,c,d){this.u!==this.g&&(this.o=this.o.concat(c),this.u++, + d&&this.o.length>=d&&(this.u=this.g),this.u===this.g&&(this.cache&&this.l.set(b,this.o),this.F&&this.F(this.o)));return this}function S(a){const b=B();for(const c in a)if(a.hasOwnProperty(c)){const d=a[c];b[c]=E(d)?d.slice(0):J(d)?S(d):d}return b}function W(a,b){const c=a.length,d=O(b),e=[];for(let f=0,g=0;f=g&&(a=a[h-(e+.5>>0)],a=a[c]||(a[c]=[]),a[a.length]= + d);return e}function ba(a,b){if(a){const c=Object.keys(a);for(let d=0,e=c.length;da?1:a?-1:0}function pa(a,b){a=a[L];b=b[L];return ab?1:0}function oa(a,b){const c=L.length;for(let d=0;db?1:0}function T(a,b,c){return a?{page:a,next:b?""+b:null,result:c}:c}function ha(a,b,c,d,e,f,g){let h,l=[];if(!0===c){c="0";var m=""}else m=c&&c.split(":");const n=a.length;if(1g&&(m=0),m=m||0,h=m+b,h=this.m.length&&(this.C=0),this.m[this.C].postMessage({add:!0,id:a,content:b}),this.c[f]=""+this.C,c&&c(),this;if(!e){if(this.async&&"function"!==typeof importScripts){let r=this;f=new Promise(function(w){setTimeout(function(){r.add(a, + b,null,d,!0);r=null;w()})});if(c)f.then(c);else return f;return this}if(c)return this.add(a,b,null,d,!0),c(),this}b=this.encode(b);if(!b.length)return this;c=this.f;e=O(c)?c(b):b.split(this.split);this.filter&&(e=W(e,this.filter));const p=B();p._ctx=B();const k=e.length,u=this.threshold,t=this.depth,A=this.b,z=this.j,y=this.D;for(let r=0;rn;x--)m=g.substring(n,x),V(z,p,m,a,w,l,u,A-1)}break;default:if(h=V(z,p,g,a,1,l,u,A-1),t&&1=u)for(h=p._ctx[g]||(p._ctx[g]=B()),g=this.i[g]||(this.i[g]=ia(A-(u||0))),l=r-t,m=r+t+1,0>l&&(l=0),m>k&&(m=k);lg;d--)e=h[d-1],h[d]=e,f[e]=d;h[g]=b;f[b]=g}}}return c};return a}();return v}(function(){const I={},R="undefined"!==typeof Blob&&"undefined"!==typeof URL&&URL.createObjectURL;return function(v,K,S,W,P){S=R?URL.createObjectURL(new Blob(["("+ + S.toString()+")()"],{type:"text/javascript"})):v+".min.js";v+="-"+K;I[v]||(I[v]=[]);I[v][P]=new Worker(S);I[v][P].onmessage=W;return I[v][P]}}()),this); diff --git a/app/styles.css b/app/styles.css index fbb4fac..6d529ad 100644 --- a/app/styles.css +++ b/app/styles.css @@ -10,6 +10,20 @@ span { box-sizing: border-box; } +pre { + color: inherit; + padding: 0 !important; + margin: 0 !important; + font: inherit; + background-color: transparent; + border: 0; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + body { background: no-repeat fixed center; background-size: cover; @@ -43,6 +57,7 @@ body { margin: 0 !important; height: 100%; } + .side-one { padding: 0; margin: 0; @@ -68,26 +83,14 @@ body { } - .heading { padding: 10px 16px 10px 15px; margin: 0 !important; height: 60px; width: 100%; background-color: #eee; - z-index: 1000; -} - -.heading-avatar { - padding: 0 !important; - cursor: pointer; - -} - -.heading-avatar-icon img { - border-radius: 50%; - height: 40px; - width: 40px; + z-index: 1; + overflow: visible; } .heading-name { @@ -107,42 +110,25 @@ body { display: block; } -#recipient-status{ +#recipient-status { border-radius: 50%; - background: none; + background: none; height: 25px; width: 25px; - color:white; + color: white; } -.heading-online { - display: none; - padding: 0 5px; - font-size: 12px; - color: #93918f; -} -.heading-compose { + +.heading-icon { padding: 0; } -.heading-compose i { +.heading-icon i { text-align: center; padding: 5px; color: #93918f; cursor: pointer; } -.heading-dot { - padding: 0; - margin-left: 10px; -} - -.heading-dot i { - text-align: right; - padding: 5px; - color: #93918f; - cursor: pointer; -} - .searchBox { padding: 0 !important; margin: 0 !important; @@ -157,7 +143,6 @@ body { background-color: #fbfbfb; } - /*#searchBox-inner input { box-shadow: none; }*/ @@ -190,17 +175,6 @@ body { background-color: #f2f2f2; } -.sideBar-avatar { - text-align: center; - padding: 0 !important; -} - -.avatar-icon img { - border-radius: 50%; - height: 49px; - width: 49px; -} - .sideBar-main { padding: 0 !important; } @@ -244,6 +218,7 @@ body { position: relative; left: -100%; } + .newMessage-heading { padding: 10px 16px 10px 15px !important; margin: 0 !important; @@ -268,6 +243,7 @@ body { font-weight: 700; padding: 10px 5px !important; } + .newMessage-back { text-align: center; vertical-align: baseline; @@ -275,6 +251,7 @@ body { display: block; cursor: pointer; } + .newMessage-back i { margin: auto !important; } @@ -328,29 +305,13 @@ body { border: 1px solid #f7f7f7; height: calc(100% - 120px); } -.message-inner{ - overflow-y: auto; -} -.message-previous { - margin : 0 !important; - padding: 0 !important; - height: auto; - width: 100%; -} -.previous { - font-size: 15px; - text-align: center; - padding: 10px !important; - cursor: pointer; -} -.previous a { - text-decoration: none; - font-weight: 700; +.message-inner { + overflow-y: auto; } .message-body { - margin: 0 !important; + margin: 5px 0px !important; padding: 0 !important; width: auto; height: auto; @@ -358,48 +319,51 @@ body { .message-main-receiver { /*padding: 10px 20px;*/ - max-width: 60%; + max-width: 80%; height: auto; } .message-main-sender { padding: 3px 20px !important; - margin-left: 40% !important; - max-width: 60%; + margin-left: 20% !important; + max-width: 80%; height: auto; } .message-text { margin: 0 !important; - padding: 5px !important; - word-wrap:break-word; + padding: 0px !important; font-weight: 200; font-size: 14px; - padding-bottom: 0 !important; height: auto; + word-break: break-word; +} + +.message-text i { + border: 0.5px solid rgba(0, 0, 0, 0.3); + border-radius: 5px; + background-color: rgba(0, 0, 0, 0.1); + padding: 5px; + display: block; + cursor: pointer; } .message-time { margin: 0 !important; - margin-left: 50px !important; font-size: 12px; text-align: right; color: #9a9a9a; height: auto; - } .receiver { float: left; width: auto !important; - padding: 4px 10px 7px !important; + padding: 5px 10px 2px !important; border-radius: 10px 10px 10px 0; background: #ffffff; font-size: 12px; - word-wrap: break-word; - display: inline-block; height: auto; - } .sender { @@ -408,13 +372,10 @@ body { height: auto; background: #dcf8c6; border-radius: 10px 10px 0 10px; - padding: 4px 10px 7px !important; + padding: 5px 10px 2px !important; font-size: 12px; - display: inline-block; - word-wrap: break-word; } - /*Reply*/ .reply { @@ -426,33 +387,12 @@ body { z-index: 1000; } -.reply-emojis { +.reply-icon { padding: 5px !important; + overflow: visible; } -.reply-emojis i { - text-align: center; - padding: 5px 5px 5px 5px !important; - color: #93918f; - cursor: pointer; -} - -.reply-recording { - padding: 5px !important; -} - -.reply-recording i { - text-align: center; - padding: 5px !important; - color: #93918f; - cursor: pointer; -} - -.reply-send { - padding: 5px !important; -} - -.reply-send i { +.reply-icon i { text-align: center; padding: 5px !important; color: #93918f; @@ -463,7 +403,7 @@ body { padding: 2px 5px !important; } -.reply-main input { +.reply-main textarea { width: 100%; resize: none; overflow: hidden; @@ -476,126 +416,108 @@ body { font-size: 16px; } -.reply-main input:focus { +.reply-main textarea:focus { outline: none; border: none; text-indent: 5px; box-shadow: none; } -@media screen and (max-width: 700px) { - .app { - top: 0; - height: 100%; - } - .heading { - height: 70px; - background-color: #009688; - } - .fa-2x { - font-size: 2.3em !important; - } - .heading-avatar { - padding: 0 !important; - } - .heading-avatar-icon img { - height: 50px; - width: 50px; - } - .heading-compose { - padding: 5px !important; - } - .heading-compose i { - color: #fff; - cursor: pointer; - } - .heading-dot { - padding: 5px !important; - margin-left: 10px !important; - } - .heading-dot i { - color: #fff; - cursor: pointer; - } - .sideBar { - height: calc(100% - 130px); - } - .sideBar-body { - height: 80px; - } - .sideBar-avatar { - text-align: left; - padding: 0 8px !important; - } - .avatar-icon img { - height: 55px; - width: 55px; - } - .sideBar-main { - padding: 0 !important; - } - .sideBar-main .row { - padding: 0 !important; - margin: 0 !important; - } - .sideBar-name { - padding: 10px 5px !important; - } - .name-meta { - font-size: 16px; - padding: 5% !important; - } - .sideBar-time { - padding: 10px !important; - } - .time-meta { - text-align: right; - font-size: 14px; - padding: 4% !important; - color: rgba(0, 0, 0, .4); - vertical-align: baseline; - } - /*Conversation*/ - .conversation { - padding: 0 !important; - margin: 0 !important; - height: 100%; - /*width: 100%;*/ - border-left: 1px solid rgba(0, 0, 0, .08); - /*overflow-y: auto;*/ - } - .message { - height: calc(100% - 140px); - } - .reply { - height: 70px; - } - .reply-emojis { - padding: 5px 0 !important; - } - .reply-emojis i { - padding: 5px 2px !important; - font-size: 1.8em !important; - } - .reply-main { - padding: 2px 8px !important; - } - .reply-main input { - padding: 8px !important; - font-size: 18px; - } - .reply-recording { - padding: 5px 0 !important; - } - .reply-recording i { - padding: 5px 0 !important; - font-size: 1.8em !important; - } - .reply-send { - padding: 5px 0 !important; - } - .reply-send i { - padding: 5px 2px 5px 0 !important; - font-size: 1.8em !important; - } +.badgebox { + opacity: 0; +} + +.badgebox+.badge { + height: 14px; + width: 20px; + font-size: 0px; + padding: 2px; + float: right; +} + +.badgebox:focus+.badge { + box-shadow: inset 0px 0px 3px; +} + +.badgebox:checked+.badge { + font-size: inherit; +} + +.listLabel { + margin: 5px; + text-align: left; + line-height: 50%; + padding: 10px; + display: block; +} + +#overlay { + width: 100%; + height: 100%; + text-align: center; + position: fixed; + top: 0; + z-index: 2; + background-color: rgba(0, 0, 0, .5); + display: none; +} + +.userChecklist-container { + margin-top: 25px; + padding: 5px; + width: 325px; + height: auto; + background-color: #eee; + display: inline-block; + +} + +#userChecklist { + height: 275px; + overflow-y: scroll; +} + +.dropdown-content { + display: none; + position: absolute; + min-width: 150px; + z-index: 1; + cursor: pointer; + margin-left: -100px; + overflow: inherit; +} + +.dropdown-content li { + color: rgb(95, 95, 95); + padding: 12px 16px; + text-decoration: none; + display: block; + text-align: right; + background-color: #ddd; +} + +.dropdown-content li:hover { + background-color: #aaa; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +#groupOptions { + display: none; + overflow: visible; +} + +input.attach-file { + display: none; +} + +input.attach-file+span { + color: #93918f; + font-size: 10px; +} + +.hidden { + display: none; } \ No newline at end of file diff --git a/app/websocket_chat b/app/websocket_chat index 9911f0d..c7b85df 100755 Binary files a/app/websocket_chat and b/app/websocket_chat differ diff --git a/util/websocket_chat.c b/util/websocket_chat.c index 9825344..886b248 100644 --- a/util/websocket_chat.c +++ b/util/websocket_chat.c @@ -22,23 +22,15 @@ static int is_websocket(const struct mg_connection *nc) { static void broadcast(struct mg_connection *nc, const struct mg_str msg) { struct mg_connection *c; - char buf[500]; - - snprintf(buf, sizeof(buf), "%.*s", (int) msg.len, msg.p); - printf("%s\n", buf); /* Local echo. */ for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { if (c == nc) continue; /* Don't send to the sender. */ - mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, msg.p, msg.len); } } static void unicast(struct mg_connection *nc,const struct mg_str msg) { - char buf[5000]; - - snprintf(buf, sizeof(buf), "%.*s", (int) msg.len, msg.p); - printf("%s\n", buf); /* Local echo. */ if(nc != NULL) - mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, msg.p, msg.len); else printf("No selfClient is connected!\n"); @@ -115,7 +107,7 @@ int main(int argc, char** argv) { nc = mg_bind(&mgr, s_http_port, ev_handler); mg_set_protocol_http_websocket(nc); - s_http_server_opts.document_root = "app"; // Serve current directory + s_http_server_opts.document_root = "."; // Serve current directory s_http_server_opts.enable_directory_listing = "no"; printf("Started on port %s\n", s_http_port);