2015-07-15 13:54:52 +00:00
|
|
|
/*! @file
|
2015-06-28 20:58:51 +00:00
|
|
|
|
2015-07-15 21:33:06 +00:00
|
|
|
@id $Id$
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
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
|
|
|
|
*/
|
|
|
|
// 1 2 3 4 5 6 7 8
|
|
|
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
/// 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. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function error(data, stay) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").addClass("error")
|
|
|
|
$("#status").removeClass("notice")
|
|
|
|
$("#status").removeClass("success")
|
2015-06-28 20:58:51 +00:00
|
|
|
if (data) {
|
|
|
|
if (typeof data == 'string') {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").html(data);
|
2015-06-28 20:58:51 +00:00
|
|
|
console.log("error: "+data);
|
|
|
|
} else {
|
2015-07-01 00:07:33 +00:00
|
|
|
$("#status").html('error');
|
2015-06-28 20:58:51 +00:00
|
|
|
console.log("error: "+JSON.stringify(data));
|
|
|
|
}
|
|
|
|
} else {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").html('error');
|
2015-06-28 20:58:51 +00:00
|
|
|
console.log("error");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
if (!stay) setTimeout(start, 5000);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Show notice messsage
|
|
|
|
/** Fades in an notice message and logs to console.
|
2015-07-15 21:33:06 +00:00
|
|
|
@param text (optional) The data is a string. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function notice(text) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").addClass("notice")
|
|
|
|
$("#status").removeClass("error")
|
|
|
|
$("#status").removeClass("success")
|
2015-06-28 20:58:51 +00:00
|
|
|
if (text) {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").html(text);
|
2015-06-28 20:58:51 +00:00
|
|
|
console.log("notice: "+text);
|
|
|
|
} else {
|
|
|
|
$("#status").html('');
|
|
|
|
console.log("notice");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Show notice messsage
|
|
|
|
/** Fades in an success message and logs to console.
|
2015-07-15 21:33:06 +00:00
|
|
|
@param text (optional) The data is a string. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function success(text) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").addClass("success")
|
|
|
|
$("#status").removeClass("error")
|
|
|
|
$("#status").removeClass("notice")
|
2015-06-28 20:58:51 +00:00
|
|
|
if (text) {
|
2015-06-29 13:19:06 +00:00
|
|
|
$("#status").html(text);
|
2015-06-28 20:58:51 +00:00
|
|
|
console.log("success: "+text);
|
|
|
|
} else {
|
|
|
|
$("#status").html('');
|
|
|
|
console.log("success");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Show status message in the main screen area
|
|
|
|
/** @param text Text is a message or some complex HTML from the server.
|
|
|
|
@param msg The success message text */
|
2015-06-28 20:58:51 +00:00
|
|
|
function status(text, msg) {
|
|
|
|
$("#main").fadeOut("slow", function() {
|
|
|
|
$("#main").html(text);
|
2015-09-24 21:47:18 +00:00
|
|
|
if (msg) success(msg);
|
|
|
|
else setTimeout("$('#status').fadeOut('slow')", 5000);
|
2015-06-29 22:34:37 +00:00
|
|
|
$("#main").fadeIn("slow", function() {
|
|
|
|
$("form input:first-child").focus();
|
|
|
|
})
|
2015-06-28 20:58:51 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-08-27 20:55:04 +00:00
|
|
|
var getLoopTimeout = null; ///< store get timeout to make sure only one is running
|
|
|
|
/// Set timeout for next get request
|
|
|
|
/** @param time timeout time in ms, defaults to 10000 */
|
|
|
|
function getLoop(time) {
|
|
|
|
if (!time) time = 10000;
|
|
|
|
getLoopStop();
|
|
|
|
getLoopTimeout = setTimeout(get, time);
|
|
|
|
}
|
|
|
|
/// Stop get loop if it is running
|
|
|
|
function getLoopStop() {
|
|
|
|
if (getLoopTimeout) clearTimeout(getLoopTimeout);
|
|
|
|
getLoopTimeout = null;
|
|
|
|
}
|
|
|
|
|
2015-08-16 15:07:17 +00:00
|
|
|
/// Alert user
|
|
|
|
/** Alert user, e.g. that a new message has arrived.
|
|
|
|
@param */
|
|
|
|
function alert() {
|
|
|
|
navigator.vibrate =
|
|
|
|
navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
|
|
|
|
if (navigator.vibrate) {
|
|
|
|
// vibration API supported
|
|
|
|
navigator.vibrate(1000);
|
|
|
|
}
|
|
|
|
(new Audio("A-Tone-His_Self-1266414414.mp3")).play();
|
|
|
|
}
|
|
|
|
|
2015-08-26 21:13:57 +00:00
|
|
|
/// Toggle Menu Display
|
|
|
|
function togglemenu() {
|
|
|
|
$("#menu").toggle();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Download Profile Backup
|
|
|
|
function backup() {
|
2015-08-27 20:55:04 +00:00
|
|
|
getLoopStop();
|
2015-09-24 21:47:18 +00:00
|
|
|
status("<p>Starting backup download ...</p>", "");
|
2015-08-26 21:13:57 +00:00
|
|
|
var download = document.createElement('a');
|
|
|
|
download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage));
|
|
|
|
download.target = '_blank';
|
|
|
|
function pad(n) {return n<10 ? '0'+n : n}
|
|
|
|
var now = new Date();
|
|
|
|
download.download =
|
|
|
|
pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+
|
2015-09-24 21:47:18 +00:00
|
|
|
"-"+userid()+"@"+window.location.hostname+".bak";
|
2015-08-26 21:13:57 +00:00
|
|
|
var clickEvent = new MouseEvent("click", {
|
|
|
|
"view": window,
|
|
|
|
"bubbles": true,
|
|
|
|
"cancelable": false
|
|
|
|
});
|
|
|
|
download.dispatchEvent(clickEvent);
|
|
|
|
togglemenu();
|
2015-08-26 22:43:02 +00:00
|
|
|
setTimeout(start, 2000);
|
2015-08-26 21:13:57 +00:00
|
|
|
}
|
|
|
|
|
2015-08-26 21:30:59 +00:00
|
|
|
/// Upload Profile Backup
|
|
|
|
function restore(evt) {
|
2015-08-27 20:55:04 +00:00
|
|
|
getLoopStop();
|
2015-09-24 21:47:18 +00:00
|
|
|
status("<p>Starting backup restore ...</p>", "");
|
2015-08-26 22:43:02 +00:00
|
|
|
if (!window.FileReader)
|
|
|
|
return error("your browser dows 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;
|
|
|
|
setTimeout(start, 2000);
|
|
|
|
}
|
|
|
|
reader.readAsText(file);
|
|
|
|
}
|
2015-08-26 21:30:59 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 19:24:49 +00:00
|
|
|
/// Configure local groups
|
|
|
|
/** ... */
|
|
|
|
function groups() {
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Check if user name is available
|
|
|
|
/** Calls checknewuser.php on server and displays an error, if the
|
|
|
|
user name is already in use. This function is used when creating a
|
|
|
|
new user. It immediately gives the user a feedback, whether the
|
|
|
|
chosen user name is available or not.
|
|
|
|
|
|
|
|
Called when user edits the user name fields.
|
|
|
|
|
|
|
|
Sets @ref username and checks @ref password - if both are well
|
|
|
|
defined, enables the submit button.
|
|
|
|
|
|
|
|
@param user User name to check. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function checkuser(user) {
|
|
|
|
$("#register").submit(function(event) {
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
$.post("checknewuser.php", {user: user})
|
|
|
|
.done(function(res) {
|
|
|
|
username=JSON.parse(res);
|
|
|
|
if (!username||username.length<1) username=null;
|
|
|
|
$("#createuser").prop("disabled", !(username && password));
|
|
|
|
if (username) {
|
|
|
|
if (password) success("user is ready to be created");
|
|
|
|
else notice("user name is available, please set password");
|
|
|
|
} else notice("user name is not available");
|
|
|
|
}).fail(function(res) {
|
|
|
|
username=null;
|
|
|
|
$("#createuser").prop("disabled", !(username && password));
|
2015-07-02 07:08:13 +00:00
|
|
|
error("offline");
|
2015-06-28 20:58:51 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Check if password is set and matches the repeated password
|
|
|
|
/** Checks if both passwords are identical and valid and gives
|
|
|
|
feedback to the user.
|
|
|
|
|
|
|
|
Called when user edits the password fields.
|
|
|
|
|
|
|
|
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. */
|
2015-06-28 20:58:51 +00:00
|
|
|
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 notice("passwords don't match");
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// 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. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function checkpartner(user) {
|
|
|
|
$("#chat").submit(function(event) {
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
$.post("checknewuser.php", {user: user})
|
|
|
|
.done(function(res) {
|
|
|
|
if (JSON.parse(res)) {
|
|
|
|
notice("receiver does not exist");
|
|
|
|
$("#send").prop("disabled", true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$("#send").prop("disabled", false);
|
|
|
|
success("receiver exists");
|
|
|
|
}).fail(function(res) {
|
2015-07-02 07:08:13 +00:00
|
|
|
error("offline", true);
|
2015-06-28 20:58:51 +00:00
|
|
|
$("#send").prop("disabled", true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Create Local Public-/Private-Key Pair
|
|
|
|
/** Called if user has not yet his keys, just generates a new key pair. */
|
|
|
|
function createkeypair(user, pwd) {
|
2015-06-28 20:58:51 +00:00
|
|
|
status("generate keys");
|
|
|
|
openpgp.generateKeyPair({
|
2015-11-03 22:02:51 +00:00
|
|
|
numBits: 4096,
|
2015-06-28 20:58:51 +00:00
|
|
|
userId: user,
|
|
|
|
passphrase: pwd
|
|
|
|
}).then(function(keyPair) {
|
|
|
|
success("keys generated");
|
2015-08-26 22:43:02 +00:00
|
|
|
localStorage["pubKey"] = keyPair.publicKeyArmored;
|
|
|
|
localStorage["privKey"] = keyPair.privateKeyArmored;
|
2015-06-28 20:58:51 +00:00
|
|
|
login();
|
|
|
|
}).catch(function(e) {
|
|
|
|
error(e);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Get Own Public Key
|
|
|
|
/** @return public key object */
|
2015-06-28 20:58:51 +00:00
|
|
|
function publicKey() {
|
2015-08-26 22:43:02 +00:00
|
|
|
if (typeof localStorage["pubKey"] == 'undefined') return null;
|
|
|
|
return openpgp.key.readArmored(localStorage["pubKey"]);
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Get Own Private Key
|
|
|
|
/** @return private key object */
|
2015-06-28 20:58:51 +00:00
|
|
|
function privateKey() {
|
2015-08-26 22:43:02 +00:00
|
|
|
if (typeof localStorage["privKey"] == 'undefined') return null;
|
|
|
|
return openpgp.key.readArmored(localStorage["privKey"]);
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Get Own User Name
|
|
|
|
/** Get user name as user id of first public key */
|
2015-06-28 20:58:51 +00:00
|
|
|
function userid() {
|
|
|
|
if (!publicKey() ||
|
|
|
|
publicKey().keys.length < 1 ||
|
|
|
|
publicKey().keys[0].getUserIds().length < 1) return null
|
|
|
|
return publicKey().keys[0].getUserIds()[0];
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Clear Message Text And Attachments
|
|
|
|
/** Does not remove the receiver's name */
|
2015-07-01 00:07:33 +00:00
|
|
|
function clearmessage() {
|
|
|
|
filecontent = new Array();
|
|
|
|
$('#preview').empty();
|
|
|
|
$("#msg").val("");
|
|
|
|
notice("message cleared");
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Display Image Attachments
|
2015-07-01 00:07:33 +00:00
|
|
|
function attachments(files, id) {
|
|
|
|
if (files) files.forEach(function(file) {
|
2015-07-08 05:48:27 +00:00
|
|
|
if (file.content.length<100000) {
|
2015-07-01 00:07:33 +00:00
|
|
|
var img = document.createElement('img');
|
2015-07-08 05:48:27 +00:00
|
|
|
img.src = file.content;
|
2015-07-01 00:07:33 +00:00
|
|
|
$(id).append(img);
|
2015-07-02 07:08:13 +00:00
|
|
|
}
|
2015-07-01 00:07:33 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// 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.
|
|
|
|
|
|
|
|
By now, only images are supported.
|
|
|
|
|
|
|
|
Stores data in global variable @ref filecontent. */
|
2015-07-08 05:48:27 +00:00
|
|
|
function fileupload(evt) {
|
2015-07-15 13:54:52 +00:00
|
|
|
if (!window.FileReader)
|
|
|
|
return error("your browser dows not support file upload", true);
|
2015-07-08 05:48:27 +00:00
|
|
|
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 ...");
|
2015-07-15 13:54:52 +00:00
|
|
|
if (!file.type.match('^image/'))
|
|
|
|
return error(file.name+": not an image", true);
|
2015-07-08 05:48:27 +00:00
|
|
|
var img = document.createElement("img");
|
|
|
|
img.onload = function() {
|
|
|
|
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({type: file.type, content: img.src});
|
|
|
|
$("#preview").append(img);
|
|
|
|
success('image of type '+file.type+' is ready to be sent');
|
|
|
|
}
|
|
|
|
img.src = canvas.toDataURL(file.type);
|
|
|
|
}
|
|
|
|
img.src=evt.target.result;
|
|
|
|
}
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Sets Receiver's Name
|
|
|
|
/** Called when clicked on a receiver's name. Sets focus to the
|
|
|
|
message text field.
|
2015-06-30 14:09:05 +00:00
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
@param name The receiver's name. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function setreceiver(name) {
|
|
|
|
$("#recv").val(name);
|
|
|
|
checkpartner(name);
|
|
|
|
$("#msg").focus();
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:02:51 +00:00
|
|
|
var userMap = null;
|
|
|
|
/// Get a user's public key.
|
|
|
|
/** The first time, gets it from the server, later from the cache. */
|
|
|
|
function getPublicKey(user) {
|
|
|
|
var deferredObject = $.Deferred();
|
|
|
|
if (userMap == null) {
|
|
|
|
if (localStorage.userMap) {
|
|
|
|
userMap = JSON.parse(localStorage.userMap);
|
|
|
|
console.log("got userMap from localStorage");
|
|
|
|
} else {
|
|
|
|
userMap = new Array();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (userMap[user]) {
|
|
|
|
console.log("user "+user+" is in cache");
|
|
|
|
deferredObject.resolve(userMap[user]);
|
|
|
|
} else {
|
|
|
|
$.post("pubkey.php", {user: user}) // get sender's key
|
|
|
|
.done(function(pk) {
|
|
|
|
console.log("got user "+user+" from server");
|
|
|
|
userMap[user] = pk;
|
|
|
|
localStorage.userMap = JSON.stringify(userMap);
|
|
|
|
deferredObject.resolve(pk);
|
|
|
|
}).fail(function(e) {
|
|
|
|
error("offline");
|
|
|
|
deferredObject.reject(e);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return deferredObject.promise();
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
var startmsg = 0; ///< number of last downloaded message
|
|
|
|
/// Poll For New Messages, Get And Show Them
|
|
|
|
/** The global variable @ref startmsg stores the id of the last
|
|
|
|
downloaded message. This function is called by timer in regulary
|
|
|
|
periods. It calls get on server and passes @ref startmsg. The
|
|
|
|
server returns all newer messages. They are then decrypted. If
|
|
|
|
decryption is successful, then the message is shown, including
|
|
|
|
attachments. If decryption fails, the message is sent to someone
|
|
|
|
else, so failure is simply ignored. Beeps a sound once, if new
|
|
|
|
messages have been displayed. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function get() {
|
2015-07-15 13:54:52 +00:00
|
|
|
var beeped = false; // beep only once
|
2015-07-02 07:08:13 +00:00
|
|
|
$.post("get.php", {start: startmsg})
|
2015-07-15 13:54:52 +00:00
|
|
|
.done(function(res) { // new messages from server received
|
2015-07-02 07:08:13 +00:00
|
|
|
var msgs = JSON.parse(res);
|
|
|
|
if (msgs) {
|
2015-07-15 13:54:52 +00:00
|
|
|
msgs.forEach(function(e) { // one single message
|
2015-07-02 07:08:13 +00:00
|
|
|
if (startmsg<Number(e.id)) startmsg = Number(e.id);
|
2015-11-03 22:02:51 +00:00
|
|
|
getPublicKey(e.user) // get sender's key
|
2015-07-02 07:08:13 +00:00
|
|
|
.done(function(pk) {
|
|
|
|
var res=JSON.parse(pk);
|
|
|
|
var key=openpgp.key.readArmored(res);
|
|
|
|
if (!res||key.err) {
|
2015-08-27 20:55:04 +00:00
|
|
|
getLoop();
|
2015-07-02 07:08:13 +00:00
|
|
|
return error("key of receiver not found", true);
|
|
|
|
}
|
|
|
|
var message = openpgp.message.readArmored(e.msg);
|
|
|
|
var privkey = privateKey().keys[0];
|
2015-07-15 13:54:52 +00:00
|
|
|
if (privkey.decrypt(password)) // prepare own key
|
2015-07-02 07:08:13 +00:00
|
|
|
openpgp.decryptAndVerifyMessage(privkey, key.keys, message)
|
2015-07-15 13:54:52 +00:00
|
|
|
.then(function(msg) { // decryption succeded
|
|
|
|
// prepend message to list of messages
|
2015-07-02 07:08:13 +00:00
|
|
|
var message = JSON.parse(msg.text);
|
|
|
|
$("#msgs") // todo: check msg.signatures[0].valid
|
|
|
|
.prepend('<div id="id'+(e.id)+'" class="msg '+
|
|
|
|
(e.user==userid()?"me":"other")+
|
|
|
|
'"><div class="header">'+
|
|
|
|
'<span class="date">'+
|
|
|
|
(new Date(1000*Number(e.time))).toLocaleString()+
|
|
|
|
'</span><span class="sender">'+
|
|
|
|
'<a href="javascript:void(0)" '+
|
|
|
|
'onclick="setreceiver(this.innerHTML)">'+
|
|
|
|
e.user+
|
2015-09-15 19:24:49 +00:00
|
|
|
'</a>'+(message.receiver?" → "+message.receiver:"")+
|
|
|
|
'</span></div>'+
|
2015-07-02 07:08:13 +00:00
|
|
|
'<div class="text">'+
|
|
|
|
message.text+
|
|
|
|
'</div></div><div class="clear"/>');
|
2015-07-15 13:54:52 +00:00
|
|
|
// show attachments
|
2015-07-02 07:08:13 +00:00
|
|
|
attachments(message.files, '#id'+e.id+' .text');
|
2015-07-15 13:54:52 +00:00
|
|
|
// calculate and show emoticons
|
2015-07-02 07:08:13 +00:00
|
|
|
$('#id'+e.id).emoticonize();
|
2015-07-15 13:54:52 +00:00
|
|
|
// beep for the first new message
|
2015-08-16 15:07:17 +00:00
|
|
|
if (!beeped) alert()
|
2015-07-02 07:08:13 +00:00
|
|
|
beeped = true;
|
2015-07-15 13:54:52 +00:00
|
|
|
success();
|
2015-07-02 07:08:13 +00:00
|
|
|
})
|
|
|
|
.catch(function(e) {
|
|
|
|
// not for me
|
2015-07-15 13:54:52 +00:00
|
|
|
success();
|
2015-07-02 07:08:13 +00:00
|
|
|
});
|
|
|
|
}).fail(function(e) {
|
2015-09-15 19:24:49 +00:00
|
|
|
error("offline");
|
2015-07-01 00:07:33 +00:00
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
});
|
2015-07-02 07:08:13 +00:00
|
|
|
}
|
|
|
|
}).fail(function(e) {
|
2015-09-15 19:24:49 +00:00
|
|
|
error("offline")
|
2015-07-02 07:08:13 +00:00
|
|
|
});
|
2015-08-27 20:55:04 +00:00
|
|
|
getLoop(); // repeat every 10 seconds
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Send Message To Server
|
|
|
|
/** User wants to send a message. Encrypt message with own private and
|
|
|
|
the receiver's public key, then send it to the server. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function sendmessage(recv, txt) {
|
2015-07-01 00:07:33 +00:00
|
|
|
notice("1/3 preparing message ...");
|
|
|
|
$("#message").fadeOut("slow");
|
2015-11-03 22:02:51 +00:00
|
|
|
getPublicKey(recv) // get receiver's public key
|
2015-07-02 07:08:13 +00:00
|
|
|
.done(function(pk) {
|
|
|
|
var res=JSON.parse(pk);
|
|
|
|
var key=openpgp.key.readArmored(res);
|
|
|
|
if (!res||key.err) {
|
|
|
|
$("#message").fadeIn("slow");
|
2015-07-15 13:54:52 +00:00
|
|
|
error("receiver's key not found", true);
|
2015-07-02 07:08:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
var privkey = privateKey().keys[0];
|
2015-07-15 13:54:52 +00:00
|
|
|
privkey.decrypt(password); // get own private key ready
|
2015-09-15 19:24:49 +00:00
|
|
|
var message = JSON.stringify({receiver: recv, text: txt, files: filecontent});
|
2015-07-02 07:08:13 +00:00
|
|
|
notice("2/3 encrypting message ...");
|
|
|
|
openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), privkey, message)
|
2015-07-15 13:54:52 +00:00
|
|
|
.then(function(msg) { // message is encrypted
|
2015-07-02 07:08:13 +00:00
|
|
|
notice("3/3 sending message ...");
|
|
|
|
$.post("send.php", {user: userid(), msg: msg})
|
2015-07-15 13:54:52 +00:00
|
|
|
.done(function(res) { // message has been sent to server
|
2015-07-02 07:08:13 +00:00
|
|
|
var st = JSON.parse(res);
|
|
|
|
if (st.success) {
|
|
|
|
$("#message").fadeIn("slow");
|
|
|
|
clearmessage();
|
|
|
|
success(st.txt);
|
|
|
|
} else {
|
|
|
|
$("#message").fadeIn("slow");
|
|
|
|
error(st.txt, true);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
error("offline", true);
|
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
})
|
2015-07-02 07:08:13 +00:00
|
|
|
.catch(function(e) {
|
|
|
|
$("#message").fadeIn("slow");
|
|
|
|
error("encryption of message failed", true);
|
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
})
|
2015-07-02 07:08:13 +00:00
|
|
|
.fail(function(e) {
|
2015-07-01 00:07:33 +00:00
|
|
|
$("#message").fadeIn("slow");
|
2015-07-02 07:08:13 +00:00
|
|
|
error("offline", true);
|
2015-06-28 20:58:51 +00:00
|
|
|
});
|
2015-07-01 00:07:33 +00:00
|
|
|
$("#message").fadeIn("slow");
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Check And Set Password
|
|
|
|
/** Check if given password matches to decrypt the private key. If so,
|
|
|
|
store it in global temporary variable @ref password and start the
|
|
|
|
chat. The password matches, when the private key can be decrypted.
|
|
|
|
|
|
|
|
@param pwd The password to check. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function setpw(pwd) {
|
|
|
|
if (privateKey().keys[0].decrypt(pwd)) {
|
2015-09-24 21:47:18 +00:00
|
|
|
success("password matches");
|
|
|
|
$("#removeKey").hide();
|
2015-06-28 20:58:51 +00:00
|
|
|
password = pwd;
|
|
|
|
chat();
|
2015-09-24 21:47:18 +00:00
|
|
|
} else {
|
|
|
|
notice("password does not match");
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Create Password Entry Field
|
|
|
|
/** Asks user for password. When user starts to enter it, it is
|
|
|
|
permanentely checked in setpw(). As soon as the password matches,
|
|
|
|
setpw() continues automatically. No submit is required by the
|
|
|
|
user. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function getpwd() {
|
2015-09-24 21:47:18 +00:00
|
|
|
$("#removeKey").show();
|
2015-06-28 20:58:51 +00:00
|
|
|
status('<form>'+
|
2015-09-24 21:47:18 +00:00
|
|
|
' <input placeholder="please enter password for user '+userid()+
|
2015-06-29 22:34:37 +00:00
|
|
|
'" id="pwd" oninput="setpw(this.value)" type="password" />'+
|
2015-06-28 20:58:51 +00:00
|
|
|
'</form>');
|
|
|
|
}
|
|
|
|
|
2015-09-24 21:47:18 +00:00
|
|
|
function deleteUser() {
|
|
|
|
var uid = userid();
|
|
|
|
localStorage.pubKey = null;
|
|
|
|
localStorage.privKey = null;
|
|
|
|
error("user "+uid+" permanentely lost");
|
|
|
|
status("Deleted User: "+uid);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeKey() {
|
|
|
|
togglemenu();
|
|
|
|
$("#removeKey").hide();
|
|
|
|
status('<h2>Password Forgotten</h2>'+
|
|
|
|
'<div class="warning"><strong>Warning!</strong>'+
|
|
|
|
'<ul><li>You loose all messages.</li>'+
|
|
|
|
'<li>You loose your account name <em>«'+userid()+'»</em>.</li>'+
|
|
|
|
'<li>You should backup now, before you continue!</li></ul></div>'+
|
|
|
|
'<p>You can only remove your local data. '+
|
|
|
|
'You will have to create a new account with a new name on the server. '+
|
|
|
|
'This means, you loose all your messages and you loose your account '+
|
|
|
|
'name <em>«'+userid()+'»</em> forever. '+
|
|
|
|
'This chat program is secure, nobody can restore your password. '+
|
|
|
|
'Without password, you can\'t prove, that you are <em>«'+userid()+'»</em>.</p>'+
|
|
|
|
'<div class="buttongroup"><p class="toolbutton bad" onclick="deleteUser()">'+
|
|
|
|
'Yes, I really forgot my password.<br/>I want to loose my data to get a new account.</p>'+
|
|
|
|
'<p class="toolbutton good" onclick="start()">'+
|
|
|
|
'No, bring me back!.<br/>I\'ll try to remember my password.</p></div>', "");
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Main Chat Window
|
|
|
|
/** Gets chat widgets from server and displays them. Starts timer for
|
|
|
|
get() which polls for new messages. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function chat() {
|
2015-09-24 21:47:18 +00:00
|
|
|
$("#username").html(userid()+"@"+window.location.hostname);
|
2015-06-28 20:58:51 +00:00
|
|
|
if (!password) return getpwd();
|
|
|
|
$.ajax({url: "chat.html", success: function(res) {
|
|
|
|
status(res);
|
2015-08-27 20:55:04 +00:00
|
|
|
getLoop(2000);
|
2015-07-02 07:08:13 +00:00
|
|
|
}}).fail(function() {
|
|
|
|
error("offline")
|
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Login User
|
|
|
|
/** This is not really a login, it is just some kind of validation.
|
|
|
|
The server does not care if a user is online or not, it is only
|
|
|
|
interesting to the client to make sure, everything is fine. User
|
|
|
|
is logged in the following way: User name and public key are sent
|
|
|
|
to the server. If the user name exists on the server and the
|
|
|
|
public key is the same, the user is considered logged in, his
|
|
|
|
credentials seem to be valid. If user does not yet exits on
|
|
|
|
server, it is created now. If user exists, but public key is
|
|
|
|
different, then this is a complete failure, something went
|
|
|
|
terribly wrong. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function login() {
|
|
|
|
status("login ...");
|
|
|
|
$.post("login.php", {user: userid(),
|
|
|
|
pubkey: localStorage.pubKey},
|
|
|
|
function(res) {
|
2015-07-02 07:08:13 +00:00
|
|
|
var st = JSON.parse(res);
|
|
|
|
if (st.success) {
|
|
|
|
status("logged in ...", st.txt);
|
2015-06-28 20:58:51 +00:00
|
|
|
chat();
|
|
|
|
} else {
|
2015-07-02 07:08:13 +00:00
|
|
|
error(st.txt);
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
2015-07-02 07:08:13 +00:00
|
|
|
})
|
|
|
|
.fail(function(e) {
|
|
|
|
error("offline");
|
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Get And Display Form To Create New User
|
|
|
|
/** Shows user creation form. On submit, a private key is generated in
|
|
|
|
createkeypair(), then login() creates the user. */
|
2015-06-28 20:58:51 +00:00
|
|
|
function newuser() {
|
|
|
|
status("new user ...");
|
|
|
|
$.ajax({url: "newuser.html", success: function(res) {
|
|
|
|
status(res);
|
2015-07-02 07:08:13 +00:00
|
|
|
}}).fail(function() {
|
|
|
|
error("offline");
|
|
|
|
});
|
2015-06-28 20:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// Initial Function: Startup
|
|
|
|
/** Decide whether to login or to create a new user */
|
2015-06-28 20:58:51 +00:00
|
|
|
function start() {
|
2015-09-24 21:47:18 +00:00
|
|
|
$("#menu").hide();
|
2015-06-28 20:58:51 +00:00
|
|
|
try {
|
|
|
|
status("Starting up ...");
|
|
|
|
if (!userid()) {
|
|
|
|
newuser();
|
|
|
|
} else {
|
|
|
|
login();
|
|
|
|
}
|
|
|
|
} catch (m) {
|
|
|
|
error(m);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-15 13:54:52 +00:00
|
|
|
/// On Load, Call @ref start
|
2015-11-04 15:48:54 +00:00
|
|
|
$(
|
|
|
|
window.onbeforeunload = function() {
|
|
|
|
return "Are you sure you want to navigate away?";
|
|
|
|
}
|
|
|
|
window.onunload = function () { // you probably don't want to leave now...
|
|
|
|
alert('You are trying to leave.');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
start();
|
|
|
|
);
|