From ded3085d90e52a7981521ba9b6455f5c28189158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=A4ckerlin?= Date: Mon, 28 Nov 2016 16:00:39 +0000 Subject: [PATCH] in the middle of rewriting it --- nodejs/database/index.js | 1 - nodejs/etc/systemd/system/safechat.service | 9 + nodejs/public/javascripts/safechat.js | 1235 +++++++++++--------- nodejs/routes/index.js | 30 +- nodejs/safechat.js | 23 +- nodejs/sockets/index.js | 185 +-- nodejs/views/index.ejs | 21 +- 7 files changed, 790 insertions(+), 714 deletions(-) create mode 100644 nodejs/etc/systemd/system/safechat.service diff --git a/nodejs/database/index.js b/nodejs/database/index.js index 11a9c5c..0346dbf 100644 --- a/nodejs/database/index.js +++ b/nodejs/database/index.js @@ -3,7 +3,6 @@ module.exports = function(config) { var fs = require('fs'); config.multipleStatements = true; var pool = mysql.createPool(config); - console.log(__dirname+'/schema.sql') pool.query(fs.readFileSync(__dirname+'/schema.sql').toString()); if (config.max_allowed_packet) pool.query("set global max_allowed_packet=?", [config.max_allowed_packet]); diff --git a/nodejs/etc/systemd/system/safechat.service b/nodejs/etc/systemd/system/safechat.service new file mode 100644 index 0000000..b5be701 --- /dev/null +++ b/nodejs/etc/systemd/system/safechat.service @@ -0,0 +1,9 @@ +[Unit] +Description=Secure and Encrypted Chat Server + +[Service] +ExecStart=/usr/bin/nodejs /usr/share/safechat/nodejs/safechat > /var/log/safechat.log +Restart=on-abort + +[Install] +WantedBy=multi-user.target diff --git a/nodejs/public/javascripts/safechat.js b/nodejs/public/javascripts/safechat.js index 16c1cf3..98f2aca 100644 --- a/nodejs/public/javascripts/safechat.js +++ b/nodejs/public/javascripts/safechat.js @@ -1,245 +1,313 @@ /*! @file - @id $Id$ - - This is the main application as it is fully run in the user's browser. - - @dot - digraph X { - - start [URL="\ref start()"]; - newuser [URL="\ref newuser()"]; - login [URL="\ref login()"]; - createkeypair [URL="\ref createkeypair()"]; - chat [URL="\ref chat()"]; - getpwd [URL="\ref getpwd()"]; - setpw [URL="\ref setpw()"]; - get [URL="\ref get()"]; - sendmessage [URL="\ref sendmessage()"]; - - start -> newuser [label="if no keys exist"]; - start -> login [label="if keys exist"]; - newuser -> createkeypair [label="on submit"]; - createkeypair -> "openpgp.generateKeyPair"; - "openpgp.generateKeyPair" -> login [label="keys generated in local store"]; - login -> chat [label="user is valid on server"]; - chat -> getpwd [label="password not yet entered"]; - getpwd -> setpw [label="on input"]; - setpw -> chat [label="password is valid"]; - chat -> chat [label="remain in chat"]; - chat -> get [label="start timer"]; - get -> get [label="restart timer"]; - chat -> sendmessage [label="on submit"]; - sendmessage -> chat [label="remain in chat"]; - } - @enddot -*/ + @id $Id$ + + This is the main application as it is fully run in the user's browser. + + @dot + digraph X { + + start [URL="\ref start()"]; + newuser [URL="\ref newuser()"]; + login [URL="\ref login()"]; + createkeypair [URL="\ref createkeypair()"]; + chat [URL="\ref chat()"]; + getpwd [URL="\ref getpwd()"]; + setpw [URL="\ref setpw()"]; + get [URL="\ref get()"]; + sendmessage [URL="\ref sendmessage()"]; + + start -> newuser [label="if no keys exist"]; + start -> login [label="if keys exist"]; + newuser -> createkeypair [label="on submit"]; + createkeypair -> "openpgp.generateKey"; + "openpgp.generateKey" -> login [label="keys generated in local store"]; + login -> chat [label="user is valid on server"]; + chat -> getpwd [label="password not yet entered"]; + getpwd -> setpw [label="on input"]; + setpw -> chat [label="password is valid"]; + chat -> chat [label="remain in chat"]; + chat -> get [label="start timer"]; + get -> get [label="restart timer"]; + chat -> sendmessage [label="on submit"]; + sendmessage -> chat [label="remain in chat"]; + } + @enddot + */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 +function SafeChatClient(success, notice, error) { + + /// Cache Client's Key from local Strorage + var k = null; + + /// Get User Key + /** @internal key ist cached in k + @return key */ + function key() { + if (k) return k + if (typeof localStorage.key == 'undefined') return null + return k = openpgp.key.readArmored(localStorage.key) + } + + /// Get Own User Name + /** Get user name as user id of first public key */ + function uid() { + if (k || key()) return k.pub.keys[0].getUserIds()[0] + return null + } + + /// Create New User + function createuser(user, email, pwd) { + notice("generating keys") + openpgp.generateKey({ + numBits: 4096, + userIds: [{name: user, email: email}], + passphrase: pwd + }).then(function(keyPair) { + success("keys generated") + localStorage.key = keyPair.privateKeyArmored + k = keyPair.key + }).catch(function(e) { + console.log(e) + error("generating key pairs failed") + }) + } + + function password(pwd) { + return (k || keys()) && k.keys[0].decrypt(pwd) + } + + /// Encrypt Message + function encrypt(targetkeys, message, done, failed) { + if (!k) return false + openpgp.encrypt({ + publicKeys: targets.keys.concat(k.keys), + privateKeys: k, + data: message, + armor: false}) + .then(done) + .else(failed) + return true + } + + /// Decrypt Message + function decrypt(message) { + if (!k) return false + + return true + } +} + var password = null; ///< password, only stored temporary, until reload var username = null; ///< username, only used during registration var filecontent = new Array(); ///< temporary storage for attachments var socket = io.connect(); +var hostname = window.location.hostname!='localhost'?window.location.hostname:'safechat.ch'; /// Padding for numbers in dates function pad(n) { - return n<10 ? '0'+n : n + return n<10 ? '0'+n : n +} + +function uid(name) { + return name+' <'+name+'@'+hostname+'>'; } /// Convert number of bytes to readable text function size(num) { - if (num>0.6*1024) { - if (num>0.6*1024*1024) { - if (num>0.6*1024*1024*1024) { - if (num>0.6*1024*1024*1024*1024) { - return Math.round(num/1024/1024/1024/1024)+"TB"; - } else { - return Math.round(num/1024/1024/1024)+"GB"; - } - } else { - return Math.round(num/1024/1024)+"MB"; - } + if (num>0.6*1024) { + if (num>0.6*1024*1024) { + if (num>0.6*1024*1024*1024) { + if (num>0.6*1024*1024*1024*1024) { + return Math.round(num/1024/1024/1024/1024)+"TB"; } else { - return Math.round(num/1024)+"kB"; + return Math.round(num/1024/1024/1024)+"GB"; } + } else { + return Math.round(num/1024/1024)+"MB"; + } } else { - return num+"B"; + return Math.round(num/1024)+"kB"; } + } else { + return num+"B"; + } } var reboottimer = null; /// Show error messsage /** Fades in an error message and logs to console. - @param data (optional) The error can be a string or any structure. - Strings are shown to the user, structures are logged only. - @param stay (optional) If not given as @c true, reloads page after 5s. */ + @param data (optional) The error can be a string or any structure. + Strings are shown to the user, structures are logged only. + @param stay (optional) If not given as @c true, reloads page after 5s. */ function error(data, stay) { - $("#status").hide(); - $("#status").addClass("error") - $("#status").removeClass("notice") - $("#status").removeClass("success") - if (data) { - if (typeof data == 'string') { - $("#status").html(data); - console.log("error: "+data); - } else { - $("#status").html('unknown error: '+JSON.stringify(data)); - console.log("error: "+JSON.stringify(data)); - } + $("#status").hide(); + $("#status").addClass("error") + $("#status").removeClass("notice") + $("#status").removeClass("success") + if (data) { + if (typeof data == 'string') { + $("#status").html(data); + console.log("error: "+data); } else { - $("#status").html('error'); - console.log("error"); - } - $("#status").show(); - if (!stay) { - console.log("reboot in 5s"); - console.log((new Error('stacktrace')).stack); - if (!reboottimer) reboottimer = setTimeout(function() { - reboottimer = null; - start(); - }, 5000); + $("#status").html('unknown error: '+JSON.stringify(data)); + console.log("error: "+JSON.stringify(data)); } + } else { + $("#status").html('error'); + console.log("error"); + } + $("#status").show(); + if (!stay) { + console.log("reboot in 5s"); + console.log((new Error('stacktrace')).stack); + if (!reboottimer) reboottimer = setTimeout(function() { + reboottimer = null; + start(); + }, 5000); + } } /// Show notice messsage /** Fades in an notice message and logs to console. - @param text (optional) The data is a string. */ + @param text (optional) The data is a string. */ function notice(text) { - $("#status").hide() - $("#status").addClass("notice") - $("#status").removeClass("error") - $("#status").removeClass("success") - if (text) { - $("#status").html(text); - console.log("notice: "+text); - } else { - $("#status").html(''); - console.log("notice"); - } - $("#status").show(); + $("#status").hide() + $("#status").addClass("notice") + $("#status").removeClass("error") + $("#status").removeClass("success") + if (text) { + $("#status").html(text); + console.log("notice: "+text); + } else { + $("#status").html(''); + console.log("notice"); + } + $("#status").show(); } /// Show notice messsage /** Fades in an success message and logs to console. - @param text (optional) The data is a string. */ + @param text (optional) The data is a string. */ function success(text) { - $("#status").hide(); - $("#status").addClass("success") - $("#status").removeClass("error") - $("#status").removeClass("notice") - if (text) { - $("#status").html(text); - console.log("success: "+text); - } else { - $("#status").html(''); - console.log("success"); - } - $("#status").show(); + $("#status").hide(); + $("#status").addClass("success") + $("#status").removeClass("error") + $("#status").removeClass("notice") + if (text) { + $("#status").html(text); + console.log("success: "+text); + } else { + $("#status").html(''); + console.log("success"); + } + $("#status").show(); } /// Show status message in the main screen area /** @param id HTML id to be shown. - @param msg The success message text */ + @param msg The success message text */ function status(id, msg) { - console.log("state: "+id); - if (msg) success(msg); else $("#status").hide(); - $("#main").children(":not(#"+id+")").hide(); - $("#main #"+id).show(); - $("#main #"+id+" form input:first-child").focus(); + console.log("state: "+id); + if (msg) success(msg); else $("#status").hide(); + $("#main").children(":not(#"+id+")").hide(); + $("#main #"+id).show(); + $("#main #"+id+" form input:first-child").focus(); } function emit(signal, data) { - console.log("<-snd "+signal); - socket.emit(signal, data); + console.log("<-snd "+signal); + socket.emit(signal, data); } function connected() { - console.log("server connected"); - $("#connectionstatus #bad").hide(); - $("#connectionstatus #good").show(); - success("server connected"); + console.log("server connected"); + $("#connectionstatus #bad").hide(); + $("#connectionstatus #good").show(); + success("server connected"); } function disconnected() { - console.log("server disconnected"); - $("#connectionstatus #good").hide(); - $("#connectionstatus #bad").show(); - error("server disconnected", true); + console.log("server disconnected"); + $("#connectionstatus #good").hide(); + $("#connectionstatus #bad").show(); + error("server disconnected", true); } function connectionstatus() { - if (socket.connected) connected(); else disconnected(); + if (socket.connected) connected(); else disconnected(); } function htmlenc(html) { - return $('
').text(html).html(); + return $('
').text(html).html(); } function htmldec(data) { - return $('
').html(data).text(); + return $('
').html(data).text(); } /// Alert user /** Alert user, e.g. that a new message has arrived. */ function beep(user) { - if (user) success("message from "+htmlenc(user)+" received"); - navigator.vibrate = - navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; - if (navigator.vibrate) { - // vibration API supported - navigator.vibrate(1000); - } - (new Audio("sounds/A-Tone-His_Self-1266414414.mp3")).play(); + if (user) success("message from "+htmlenc(user)+" received"); + navigator.vibrate = + navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; + if (navigator.vibrate) { + // vibration API supported + navigator.vibrate(1000); + } + (new Audio("sounds/A-Tone-His_Self-1266414414.mp3")).play(); } /// Toggle Menu Display function togglemenu() { - $("#menu").toggle(); + $("#menu").toggle(); } /// Download Profile Backup function backup() { - var download = document.createElement('a'); - download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage)); - download.target = '_blank'; - var now = new Date(); - download.download = - pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+ - "-"+userid()+"@"+window.location.hostname+".bak"; - var clickEvent = new MouseEvent("click", { - "view": window, - "bubbles": true, - "cancelable": false - }); - download.dispatchEvent(clickEvent); - togglemenu(); + var download = document.createElement('a'); + download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage)); + download.target = '_blank'; + var now = new Date(); + download.download = + pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+ + "-"+userid()+"@"+hostname+".bak"; + var clickEvent = new MouseEvent("click", { + "view": window, + "bubbles": true, + "cancelable": false + }); + download.dispatchEvent(clickEvent); + togglemenu(); } /// Upload Profile Backup function restore(evt) { - if (!window.FileReader) - return error("your browser does not support file upload", true); - for (var i=0, f; f=evt.target.files[i]; ++i) { - var file = f; - var reader = new FileReader(); - reader.onload = function(evt) { - if (evt.target.error) return error("error reading file", true); - if (evt.target.readyState==0) return notice("waiting for data …"); - if (evt.target.readyState==1) return notice("loading data …"); - var parsed=JSON.parse(evt.target.result); - togglemenu(); - localStorage.pubkey = parsed.pubkey; - localStorage.privkey = parsed.privkey; - success("backup is restored"); - console.log("reboot after restore in 2s"); - if (!reboottimer) reboottimer = setTimeout(function() { - reboottimer = null; - start(); - }, 2000); - } - reader.readAsText(file); + if (!window.FileReader) + return error("your browser does not support file upload", true); + for (var i=0, f; f=evt.target.files[i]; ++i) { + var file = f; + var reader = new FileReader(); + reader.onload = function(evt) { + if (evt.target.error) return error("error reading file", true); + if (evt.target.readyState==0) return notice("waiting for data …"); + if (evt.target.readyState==1) return notice("loading data …"); + var parsed=JSON.parse(evt.target.result); + togglemenu(); + localStorage.pubkey = parsed.pubkey; + localStorage.privkey = parsed.privkey; + success("backup is restored"); + console.log("reboot after restore in 2s"); + if (!reboottimer) reboottimer = setTimeout(function() { + reboottimer = null; + start(); + }, 2000); } + reader.readAsText(file); + } } /// Configure local groups @@ -249,573 +317,582 @@ function groups() { /// Check if password is set and matches the repeated password /** Checks if both passwords are identical and valid and gives - feedback to the user. + feedback to the user. - Called when user edits the password fields. + Called when user edits the password fields. - Sets @ref username and checks @ref password - if both are well - defined, enables the submit button. + Sets @ref username and checks @ref password - if both are well + defined, enables the submit button. - @param pwd The password. - @param pwd2 The repeated password. */ + @param pwd The password. + @param pwd2 The repeated password. */ function checkpwd(pwd, pwd2) { - $("#register").submit(function(event) { - return false; - }); - if (pwd==pwd2) password=pwd; - else password=null; - if (!password||password.length<1) password=null; - $("#createuser").prop("disabled", !(username && password)); - if (password) { - if (username) success("user is ready to be created"); - else notice("password matches, please chose a valid user name"); - } else { - if (username) notice("passwords don't match"); - else if ($('#user').val()) notice("user name is already in use"); - else notice("please chose a user name"); - - } + $("#register").submit(function(event) { + return false; + }); + if (pwd==pwd2) password=pwd; + else password=null; + if (!password||password.length<1) password=null; + $("#createuser").prop("disabled", !(username && password)); + if (password) { + if (username) success("user is ready to be created"); + else notice("password matches, please chose a valid user name"); + } else { + if (username) notice("passwords don't match"); + else if ($('#user').val()) notice("user name is already in use"); + else notice("please chose a user name"); + + } } /// Checks if the receiver of a message exists on server. /** Calls checknewuser.php on server and enables the message submit - button if the receiver of the message exists on the server. */ + button if the receiver of the message exists on the server. */ function checkpartner(user) { - $("#chat").submit(function(event) { - return false; - }); - emit("user", user); + $("#chat").submit(function(event) { + return false; + }); + emit("user", uid(user)); } /// Create Local Public-/Private-Key Pair /** Called if user has not yet his keys, just generates a new key pair. */ function createkeypair(user, pwd) { - notice("generating keys"); - openpgp.generateKeyPair({ - numBits: 4096, - userId: user, - passphrase: pwd - }).then(function(keyPair) { - success("keys generated"); - localStorage.pubkey = keyPair.publicKeyArmored; - localStorage.privkey = keyPair.privateKeyArmored; - login(); - }).catch(function(e) { - error("generating key pairs failed"); - }); + notice("generating keys"); + openpgp.generateKey({ + numBits: 4096, + userIds: [{name: user, email: user+'@'+hostname}], + passphrase: pwd + }).then(function(keyPair) { + success("keys generated"); + localStorage.pubkey = keyPair.publicKeyArmored; + localStorage.privkey = keyPair.privateKeyArmored; + login(); + }).catch(function(e) { + console.log(e) + error("generating key pairs failed") + }); } /// Get Own Public Key /** @return public key object */ function publicKey() { - if (typeof localStorage.pubkey == 'undefined') { - if (typeof localStorage.pubKey == 'undefined') { - return null; - } else { - localStorage.pubkey = localStorage.pubKey; - localStorage.removeItem(pubKey); - } + if (typeof localStorage.pubkey == 'undefined') { + if (typeof localStorage.pubKey == 'undefined') { + return null; + } else { + localStorage.pubkey = localStorage.pubKey; + localStorage.removeItem(pubKey); } - return openpgp.key.readArmored(localStorage.pubkey); + } + return openpgp.key.readArmored(localStorage.pubkey); } /// Get Own Private Key /** @return private key object */ function privateKey() { - if (typeof localStorage.privkey == 'undefined') { - if (typeof localStorage.privKey == 'undefined') { - return null; - } else { - localStorage.privkey = localStorage.privKey; - localStorage.removeItem(privKey); - } + if (typeof localStorage.privkey == 'undefined') { + if (typeof localStorage.privKey == 'undefined') { + return null; + } else { + localStorage.privkey = localStorage.privKey; + localStorage.removeItem(privKey); } - return openpgp.key.readArmored(localStorage.privkey); + } + return openpgp.key.readArmored(localStorage.privkey); } /// Get Own User Name /** Get user name as user id of first public key */ function userid() { - if (!publicKey() || - publicKey().keys.length < 1 || - publicKey().keys[0].getUserIds().length < 1) return null - return publicKey().keys[0].getUserIds()[0]; + if (!publicKey() || + publicKey().keys.length < 1 || + publicKey().keys[0].getUserIds().length < 1) return null + return publicKey().keys[0].getUserIds()[0]; } /// Clear Message Text And Attachments /** Does not remove the receiver's name */ function clearmessage() { - $("#message").prop(":disabled", true); - filecontent = new Array(); - $('#preview').empty(); - $("#msg").val(""); - $("#message").prop(":disabled", false); + $("#message").prop(":disabled", true); + filecontent = new Array(); + $('#preview').empty(); + $("#msg").val(""); + $("#message").prop(":disabled", false); } function guessfilename(mimetype, user, date) { - if (!user) user = userid(); - if (!date) date = new Date(); - var ext = mimetype.replace(/.*\/(x-)?/i, ""); - return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate()) - +"-"+ext+"-"+user+"@"+window.location.hostname+'.'+ext; + if (!user) user = userid(); + if (!date) date = new Date(); + var ext = mimetype.replace(/.*\/(x-)?/i, ""); + return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate()) + +"-"+ext+"-"+user+"@"+hostname+'.'+ext; } /// Display Image Attachments function attachments(files, id, from, date) { - if (files) files.forEach(function(file) { - console.log(file); - if (!file.name) file.name = guessfilename(file.type, from, date); - var a = document.createElement('a'); - a.href = file.content; - a.download = file.name; - a.target = '_blank'; - if (file.type.match('^image/')) { - var img = document.createElement('img'); - img.title = file.name; - img.src = file.content; - a.appendChild(img); - } else if (file.type.match('^video/')) { - var video = document.createElement('video'); - video.controls = true; - video.title = file.name; - video.src = file.content; - a.appendChild(video); - } else { - var img = document.createElement('img'); - img.title = file.name; - img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg"; - a.appendChild(img); - } - $(id).append(a); - }); + if (files) files.forEach(function(file) { + console.log(file); + if (!file.name) file.name = guessfilename(file.type, from, date); + var a = document.createElement('a'); + a.href = file.content; + a.download = file.name; + a.target = '_blank'; + if (file.type.match('^image/')) { + var img = document.createElement('img'); + img.title = file.name; + img.src = file.content; + a.appendChild(img); + } else if (file.type.match('^video/')) { + var video = document.createElement('video'); + video.controls = true; + video.title = file.name; + video.src = file.content; + a.appendChild(video); + } else { + var img = document.createElement('img'); + img.title = file.name; + img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg"; + a.appendChild(img); + } + $(id).append(a); + }); } var recorder; function done() { - if (recorder) { - recorder.stop(); - recorder.recording(function(data) { - previewfile(data, "video/webm"); - abort(); - }); - } + if (recorder) { + recorder.stop(); + recorder.recording(function(data) { + previewfile(data, "video/webm"); + abort(); + }); + } } function abort() { - if (recorder) { - $("#videorecorder").hide(); - recorder.release(); - delete recorder; recorder = null; - } + if (recorder) { + $("#videorecorder").hide(); + recorder.release(); + delete recorder; recorder = null; + } } /// Record Video from builtin camera function recordvideo() { - try { - abort(); - $("#videorecorder").show(); - recorder = new MediaStreamRecorder({ - video: { - width: {ideal: 180}, - height: {ideal: 160} - }, - audio: true - }); - recorder.on("ready", function() { - $("#videorecorder video").attr("src", recorder.preview()); - $("#videorecorder video").css("width", 180); - $("#videorecorder video").css("height", 160); - $("#videorecorder video").attr("width", 180); - $("#videorecorder video").attr("height", 160); - recorder.start(); - }); - } catch (e) { - console.log(e); - error("cannot access camera", true); - } + try { + abort(); + $("#videorecorder").show(); + recorder = new MediaStreamRecorder({ + video: { + width: {ideal: 180}, + height: {ideal: 160} + }, + audio: true + }); + recorder.on("ready", function() { + $("#videorecorder video").attr("src", recorder.preview()); + $("#videorecorder video").css("width", 180); + $("#videorecorder video").css("height", 160); + $("#videorecorder video").attr("width", 180); + $("#videorecorder video").attr("height", 160); + recorder.start(); + }); + } catch (e) { + console.log(e); + error("cannot access camera", true); + } } function previewfile(content, type, name) { - if (!name) name = guessfilename(type); - if (type.match('^image/')) { - var img = document.createElement("img"); - img.onload = function() { // resize image to maximum 400px - var MAX = 400; - var width = img.width; - var height = img.height; - if (width > MAX) { - height *= MAX / width; - width = MAX; - } - if (height > MAX) { - width *= MAX / height; - height = MAX; - } - var canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - var ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0, width, height); - img.onload = function() { - filecontent.push({name: name, type: type, content: img.src}); - $("#preview").append(img); - success('image is ready to be sent'); - } - img.src = canvas.toDataURL(file.type); - img.title = name+"\n"+size(img.src.length); - } - img.src=content; - } else if (type.match('^video/')) { - filecontent.push({name: name, type: type, content: content}); - var video = document.createElement("video"); - video.setAttribute("controls", "controls"); - video.setAttribute("loop", "loop"); - video.setAttribute("src", content); - video.setAttribute("title", name+"\n"+size(content.length)); - $("#preview").append(video); - } else { - filecontent.push({name: name, type: type, content: content}); - var img = document.createElement("img"); - img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg"; - img.title = name+"\n"+size(content.length); + if (!name) name = guessfilename(type); + if (type.match('^image/')) { + var img = document.createElement("img"); + img.onload = function() { // resize image to maximum 400px + var MAX = 400; + var width = img.width; + var height = img.height; + if (width > MAX) { + height *= MAX / width; + width = MAX; + } + if (height > MAX) { + width *= MAX / height; + height = MAX; + } + var canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, width, height); + img.onload = function() { + filecontent.push({name: name, type: type, content: img.src}); $("#preview").append(img); + success('image is ready to be sent'); + } + img.src = canvas.toDataURL(file.type); + img.title = name+"\n"+size(img.src.length); } + img.src=content; + } else if (type.match('^video/')) { + filecontent.push({name: name, type: type, content: content}); + var video = document.createElement("video"); + video.setAttribute("controls", "controls"); + video.setAttribute("loop", "loop"); + video.setAttribute("src", content); + video.setAttribute("title", name+"\n"+size(content.length)); + $("#preview").append(video); + } else { + filecontent.push({name: name, type: type, content: content}); + var img = document.createElement("img"); + img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg"; + img.title = name+"\n"+size(content.length); + $("#preview").append(img); + } } - + /// Upload Attachment /** Prepares attachment to be sent in a message. If the attachment is - an image, it resizes the image to 400px on the lager side. + an image, it resizes the image to 400px on the lager side. - By now, only images are supported. + By now, only images are supported. - Stores data in global variable @ref filecontent. */ + Stores data in global variable @ref filecontent. */ function fileupload(evt) { - if (!window.FileReader) - return error("your browser does not support file upload", true); - for (var i=0, f; f=evt.target.files[i]; ++i) { - var file = f; - var reader = new FileReader(); - reader.onload = function(evt) { - if (evt.target.error) return error("error reading file", true); - if (evt.target.readyState==0) return notice("waiting for data …"); - if (evt.target.readyState==1) return notice("loading data …"); - previewfile(evt.target.result, file.type, file.name); - } - reader.readAsDataURL(file); + if (!window.FileReader) + return error("your browser does not support file upload", true); + for (var i=0, f; f=evt.target.files[i]; ++i) { + var file = f; + var reader = new FileReader(); + reader.onload = function(evt) { + if (evt.target.error) return error("error reading file", true); + if (evt.target.readyState==0) return notice("waiting for data …"); + if (evt.target.readyState==1) return notice("loading data …"); + previewfile(evt.target.result, file.type, file.name); } + reader.readAsDataURL(file); + } } /// Sets Receiver's Name /** Called when clicked on a receiver's name. Sets focus to the - message text field. + message text field. - @param name The receiver's name. */ + @param name The receiver's name. */ function setreceiver(name) { - $("#recv").val(name); - checkpartner(name); - $("#msg").focus(); + $("#recv").val(name); + checkpartner(name); + $("#msg").focus(); } var userMap = null; function users(userlist) { - console.log("rcv-> users"); - userMap = new Array(); - $("#allusers").empty(); - userlist.forEach(function(usr) { - userMap[usr.name] = usr.pubkey; - $("#allusers").append('