542 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			542 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*! @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
 | 
						|
*/
 | 
						|
//       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. */
 | 
						|
function error(data, stay) {
 | 
						|
    $("#status").fadeOut("slow", function() {
 | 
						|
        $("#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: "+JSON.stringify(data));
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            $("#status").html('error');
 | 
						|
            console.log("error");
 | 
						|
        }
 | 
						|
        $("#status").fadeIn("slow");
 | 
						|
        if (!stay) setTimeout(start, 5000);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// Show notice messsage
 | 
						|
/** Fades in an notice message and logs to console.
 | 
						|
    @param text (optional) The data is a string. */
 | 
						|
function notice(text) {
 | 
						|
    $("#status").fadeOut("slow", function() {
 | 
						|
        $("#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").fadeIn("slow");
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// Show notice messsage
 | 
						|
/** Fades in an success message and logs to console.
 | 
						|
    @param text (optional) The data is a string. */
 | 
						|
function success(text) {
 | 
						|
    $("#status").fadeOut("slow", function() {
 | 
						|
        $("#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").fadeIn("slow");
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// 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 */
 | 
						|
function status(text, msg) {
 | 
						|
    $("#main").fadeOut("slow", function() {
 | 
						|
        $("#main").html(text);
 | 
						|
        success(msg);
 | 
						|
        $("#main").fadeIn("slow", function() {
 | 
						|
            $("form input:first-child").focus();
 | 
						|
        })
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
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));
 | 
						|
            error("offline");
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
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");
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
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) {
 | 
						|
            error("offline", true);
 | 
						|
            $("#send").prop("disabled", true);
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
/// Create Local Public-/Private-Key Pair
 | 
						|
/** Called if user has not yet his keys, just generates a new key pair. */
 | 
						|
function createkeypair(user, pwd) {
 | 
						|
    status("generate keys");
 | 
						|
    openpgp.generateKeyPair({
 | 
						|
        numBits: 1024,
 | 
						|
        userId: user,
 | 
						|
        passphrase: pwd
 | 
						|
    }).then(function(keyPair) {
 | 
						|
        success("keys generated");
 | 
						|
        localStorage.pubKey = keyPair.publicKeyArmored;
 | 
						|
        localStorage.privKey = keyPair.privateKeyArmored;
 | 
						|
        login();
 | 
						|
    }).catch(function(e) {
 | 
						|
        error(e);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// Get Own Public Key
 | 
						|
/** @return public key object */
 | 
						|
function publicKey() {
 | 
						|
    if (typeof localStorage.pubKey == 'undefined') return null;
 | 
						|
    return openpgp.key.readArmored(localStorage.pubKey);
 | 
						|
}
 | 
						|
 | 
						|
/// Get Own Private Key
 | 
						|
/** @return private key object */
 | 
						|
function privateKey() {
 | 
						|
    if (typeof localStorage.privKey == 'undefined') return null;
 | 
						|
    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];
 | 
						|
}
 | 
						|
 | 
						|
/// Clear Message Text And Attachments
 | 
						|
/** Does not remove the receiver's name */
 | 
						|
function clearmessage() {
 | 
						|
    filecontent = new Array();
 | 
						|
    $('#preview').empty();
 | 
						|
    $("#msg").val("");
 | 
						|
    notice("message cleared");
 | 
						|
}
 | 
						|
 | 
						|
/// Display Image Attachments
 | 
						|
function attachments(files, id) {
 | 
						|
    if (files) files.forEach(function(file) {
 | 
						|
        if (file.content.length<100000) {
 | 
						|
            var img = document.createElement('img');
 | 
						|
            img.src = file.content;
 | 
						|
            $(id).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.
 | 
						|
 | 
						|
    By now, only images are supported.
 | 
						|
 | 
						|
    Stores data in global variable @ref filecontent. */
 | 
						|
function fileupload(evt) {
 | 
						|
    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 ...");
 | 
						|
            if (!file.type.match('^image/'))
 | 
						|
                return error(file.name+": not an image", true);
 | 
						|
            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);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// Sets Receiver's Name
 | 
						|
/** Called when clicked on a receiver's name. Sets focus to the
 | 
						|
    message text field.
 | 
						|
 | 
						|
    @param name The receiver's name. */
 | 
						|
function setreceiver(name) {
 | 
						|
    $("#recv").val(name);
 | 
						|
    checkpartner(name);
 | 
						|
    $("#msg").focus();
 | 
						|
}
 | 
						|
 | 
						|
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. */
 | 
						|
function get() {
 | 
						|
    var beeped = false; // beep only once
 | 
						|
    $.post("get.php", {start: startmsg})
 | 
						|
        .done(function(res) { // new messages from server received
 | 
						|
            var msgs = JSON.parse(res);
 | 
						|
            if (msgs) {
 | 
						|
                msgs.forEach(function(e) { // one single message
 | 
						|
                    if (startmsg<Number(e.id)) startmsg = Number(e.id);
 | 
						|
                    $.post("pubkey.php", {user: e.user}) // get sender's key
 | 
						|
                        .done(function(pk) {
 | 
						|
                            var res=JSON.parse(pk);
 | 
						|
                            var key=openpgp.key.readArmored(res);
 | 
						|
                            if (!res||key.err) {
 | 
						|
                                setTimeout(get, 10000);
 | 
						|
                                return error("key of receiver not found", true);
 | 
						|
                            }
 | 
						|
                            var message = openpgp.message.readArmored(e.msg);
 | 
						|
                            var privkey = privateKey().keys[0];
 | 
						|
                            if (privkey.decrypt(password)) // prepare own key
 | 
						|
                                openpgp.decryptAndVerifyMessage(privkey, key.keys, message)
 | 
						|
                                .then(function(msg) { // decryption succeded
 | 
						|
                                    // prepend message to list of messages
 | 
						|
                                    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+
 | 
						|
                                                 '</a></span></div>'+
 | 
						|
                                                 '<div class="text">'+
 | 
						|
                                                 message.text+
 | 
						|
                                                 '</div></div><div class="clear"/>');
 | 
						|
                                    // show attachments
 | 
						|
                                    attachments(message.files, '#id'+e.id+' .text');
 | 
						|
                                    // calculate and show emoticons
 | 
						|
                                    $('#id'+e.id).emoticonize();
 | 
						|
                                    // beep for the first new message
 | 
						|
                                    if (!beeped)
 | 
						|
                                        (new Audio("A-Tone-His_Self-1266414414.mp3"))
 | 
						|
                                        .play();
 | 
						|
                                    beeped = true;
 | 
						|
                                    success();
 | 
						|
                                })
 | 
						|
                                .catch(function(e) {
 | 
						|
                                    // not for me
 | 
						|
                                    success();
 | 
						|
                                });
 | 
						|
                        }).fail(function(e) {
 | 
						|
                            error("offline", true);
 | 
						|
                        });
 | 
						|
                });
 | 
						|
            }
 | 
						|
        }).fail(function(e) {
 | 
						|
            error("offline", true)
 | 
						|
        });
 | 
						|
    setTimeout(get, 10000); // repeat every 10 seconds
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
function sendmessage(recv, txt) {
 | 
						|
    notice("1/3 preparing message ...");
 | 
						|
    $("#message").fadeOut("slow");
 | 
						|
    $.post("pubkey.php", {user: recv}) // get receiver's public key
 | 
						|
        .done(function(pk) {
 | 
						|
            var res=JSON.parse(pk);
 | 
						|
            var key=openpgp.key.readArmored(res);
 | 
						|
            if (!res||key.err) {
 | 
						|
                $("#message").fadeIn("slow");
 | 
						|
                error("receiver's key not found", true);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            var privkey = privateKey().keys[0];
 | 
						|
            privkey.decrypt(password); // get own private key ready
 | 
						|
            var message = JSON.stringify({text: txt, files: filecontent});
 | 
						|
            notice("2/3 encrypting message ...");
 | 
						|
            openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), privkey, message)
 | 
						|
                .then(function(msg) { // message is encrypted
 | 
						|
                    notice("3/3 sending message ...");
 | 
						|
                    $.post("send.php", {user: userid(), msg: msg})
 | 
						|
                        .done(function(res) { // message has been sent to server
 | 
						|
                            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);
 | 
						|
                        });
 | 
						|
                })
 | 
						|
                .catch(function(e) {
 | 
						|
                    $("#message").fadeIn("slow");
 | 
						|
                    error("encryption of message failed", true);
 | 
						|
                });
 | 
						|
        })
 | 
						|
        .fail(function(e) {
 | 
						|
            $("#message").fadeIn("slow");
 | 
						|
            error("offline", true);
 | 
						|
        });
 | 
						|
    $("#message").fadeIn("slow");
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
function setpw(pwd) {
 | 
						|
    if (privateKey().keys[0].decrypt(pwd)) {
 | 
						|
        password = pwd;
 | 
						|
        chat();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
function getpwd() {
 | 
						|
    status('<form>'+
 | 
						|
           '    <input placeholder="password for '+userid()+
 | 
						|
           '" id="pwd" oninput="setpw(this.value)" type="password" />'+
 | 
						|
           '</form>');
 | 
						|
}
 | 
						|
 | 
						|
/// Main Chat Window
 | 
						|
/** Gets chat widgets from server and displays them. Starts timer for
 | 
						|
    get() which polls for new messages. */
 | 
						|
function chat() {
 | 
						|
    if (!password) return getpwd();
 | 
						|
    $.ajax({url: "chat.html", success: function(res) {
 | 
						|
        status(res);
 | 
						|
        setTimeout(get, 2000);
 | 
						|
    }}).fail(function() {
 | 
						|
        error("offline")
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
function login() {
 | 
						|
    status("login ...");
 | 
						|
    $.post("login.php", {user: userid(),
 | 
						|
                         pubkey: localStorage.pubKey},
 | 
						|
           function(res) {
 | 
						|
               var st = JSON.parse(res);
 | 
						|
               if (st.success) {
 | 
						|
                   status("logged in ...", st.txt);
 | 
						|
                   chat();
 | 
						|
               } else {
 | 
						|
                   error(st.txt);
 | 
						|
               }
 | 
						|
           })
 | 
						|
        .fail(function(e) {
 | 
						|
            error("offline");
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
/// 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. */
 | 
						|
function newuser() {
 | 
						|
    status("new user ...");
 | 
						|
    $.ajax({url: "newuser.html", success: function(res) {
 | 
						|
        status(res);
 | 
						|
    }}).fail(function() {
 | 
						|
        error("offline");
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/// Initial Function: Startup
 | 
						|
/** Decide whether to login or to create a new user */
 | 
						|
function start() {
 | 
						|
    try {
 | 
						|
        status("Starting up ...");
 | 
						|
        if (!userid()) {
 | 
						|
            newuser();
 | 
						|
        } else {
 | 
						|
            login();
 | 
						|
        }
 | 
						|
    } catch (m) {
 | 
						|
        error(m);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// On Load, Call @ref start
 | 
						|
$(start);
 |