/ * ! @ f i l e
@ 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
var socket = io . connect ( ) ;
/// Padding for numbers in dates
function pad ( n ) {
return n < 10 ? '0' + n : n
}
/// 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" ;
}
} else {
return Math . round ( num / 1024 ) + "kB" ;
}
} else {
return num + "B" ;
}
}
var reboottimer = null ;
/// Show error messsage
/ * * F a d e s i n a n e r r o r m e s s a g e a n d l o g s t o c o n s o l e .
@ 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 5 s . * /
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 ) ) ;
}
} 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
/ * * F a d e s i n a n n o t i c e m e s s a g e a n d l o g s t o c o n s o l e .
@ 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 ( ) ;
}
/// Show notice messsage
/ * * F a d e s i n a n s u c c e s s m e s s a g e a n d l o g s t o c o n s o l e .
@ 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 ( ) ;
}
/// Show status message in the main screen area
/ * * @ p a r a m i d H T M L i d t o b e s h o w n .
@ 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 ( ) ;
}
function 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" ) ;
}
function disconnected ( ) {
console . log ( "server disconnected" ) ;
$ ( "#connectionstatus #good" ) . hide ( ) ;
$ ( "#connectionstatus #bad" ) . show ( ) ;
error ( "server disconnected" , true ) ;
}
function connectionstatus ( ) {
if ( socket . connected ) connected ( ) ; else disconnected ( ) ;
}
function htmlenc ( html ) {
return $ ( '<div/>' ) . text ( html ) . html ( ) ;
}
function htmldec ( data ) {
return $ ( '<div/>' ) . 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 ( ) ;
}
/// Toggle Menu Display
function togglemenu ( ) {
$ ( "#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 ( ) ;
}
/// 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 ) ;
}
}
/// Configure local groups
/** … */
function groups ( ) {
}
/// Check if password is set and matches the repeated password
/ * * C h e c k s i f b o t h p a s s w o r d s a r e i d e n t i c a l a n d v a l i d a n d g i v e s
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 {
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.
/ * * C a l l s c h e c k n e w u s e r . p h p o n s e r v e r a n d e n a b l e s t h e m e s s a g e s u b m i t
button if the receiver of the message exists on the server . * /
function checkpartner ( user ) {
$ ( "#chat" ) . submit ( function ( event ) {
return false ;
} ) ;
emit ( "user" , 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" ) ;
} ) ;
}
/// 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 ) ;
}
}
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 ) ;
}
}
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 ( ) {
$ ( "#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 ;
}
/// 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 ) ;
} ) ;
}
var recorder ;
function done ( ) {
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 ;
}
}
/// 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 ) ;
}
}
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 ) ;
$ ( "#preview" ) . append ( img ) ;
}
}
/// Upload Attachment
/ * * P r e p a r e s a t t a c h m e n t t o b e s e n t i n a m e s s a g e . I f t h e a t t a c h m e n t i s
an image , it resizes the image to 400 px 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 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
/ * * C a l l e d w h e n c l i c k e d o n a r e c e i v e r ' s n a m e . S e t s f o c u s t o t h e
message text field .
@ param name The receiver ' s name . * /
function setreceiver ( name ) {
$ ( "#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 ( '<option value="' + htmlenc ( usr . name ) + '">' )
$ ( "#allusers" ) . hide ( ) ;
console . log ( " user: " + usr . name ) ;
} ) ;
localStorage . userMap = JSON . stringify ( userMap ) ;
}
function fail ( msg ) {
console . log ( "rcv-> fail" ) ;
error ( msg ) ;
}
function loggedin ( ) {
console . log ( "rcv-> login" ) ;
success ( "login successful" ) ;
chat ( ) ;
}
function user ( usr ) {
if ( usr . exists ) console . log ( "rcv-> user(" + usr . name + ")" ) ;
else console . log ( "rcv-> user(" + usr . name + "): name is available" ) ;
if ( $ ( "#newuser" ) . is ( ":visible" ) && usr . name == $ ( '#user' ) . val ( ) ) {
// same username as in the create user form
$ ( "#createuser" ) . prop ( "disabled" , usr . exists ) ; // todo: check password
if ( ! usr . exists ) {
username = usr . name ;
success ( "user name " + usr . name + " is available" ) ;
} else {
username = null ;
error ( "user name " + usr . name + " is in use" , true ) ;
}
}
if ( $ ( "#chat" ) . is ( ":visible" ) && usr . name == $ ( "#recv" ) . val ( ) ) { // same username as in receiver
$ ( '#send' ) . prop ( "disabled" , ! usr . exists ) ;
$ ( "label[for=send] img" ) . css ( "opacity" , usr . exists ? "1.0" : "0.4" ) ;
$ ( "label[for=send] img" ) . css ( "filter" , usr . exists ? "alpha(opacity=100)" : "alpha(opacity=40)" ) ;
if ( usr . exists ) success ( "recipient exists" ) ;
else error ( "unknown recipient" , true ) ;
}
if ( userMap == null ) {
if ( localStorage . userMap ) {
userMap = JSON . parse ( localStorage . userMap ) ;
} else {
userMap = new Array ( ) ;
}
}
if ( usr . exists && usr . pubkey && userMap [ usr . name ] != usr . pubkey ) {
userMap [ usr . name ] = usr . pubkey ;
$ ( "#allusers" ) . append ( 'option value="' + htmlenc ( usr . name ) + '"' )
localStorage . userMap = JSON . stringify ( userMap ) ;
}
}
/// 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 && userMap [ user ] ) deferredObject . resolve ( userMap [ user ] ) ;
else deferredObject . reject ( "unknown user" ) ;
return deferredObject . promise ( ) ;
}
/// Received a list of messages from server
function messages ( msgs ) {
console . log ( "rcv-> messages(" + msgs . length + ")" ) ;
if ( ! password || ! privateKey ( ) )
return setTimeout ( function ( ) { emit ( "messages" ) ; } , 1000 ) ; // try again later
status ( "allmessages" ) ;
notice ( "load messages, please wait …" ) ;
msgs . forEach ( function ( msg ) { message ( msg , true ) ; } ) ;
status ( "chat" ) ;
}
/// Received a message from server
function message ( m , internal ) {
if ( ! internal ) console . log ( "rcv-> message(" + m . user + ")" ) ;
if ( ! password || ! privateKey ( ) ) return ;
var key = openpgp . key . readArmored ( m . pubkey ) ;
if ( key . err ) return error ( "key of sender unreadable" , true ) ;
var message = openpgp . message . readArmored ( m . 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' + ( m . id ) + '" class="msg ' +
( m . user == userid ( ) ? "me" : "other" ) +
'"><div class="header">' +
'<span class="date">' +
( new Date ( m . time ) ) . toLocaleString ( ) +
'</span><span class="sender">' +
'<a href="javascript:void(0)" ' +
'onclick="setreceiver(this.innerHTML)">' +
htmlenc ( m . user ) +
'</a>' + ( message . receiver ? ' → <a href="javascript:void(0)" ' +
'onclick="setreceiver(this.innerHTML)">'
+ htmlenc ( message . receiver ) + '</a>' : "" ) +
'</span></div>' +
'<div class="text">' +
htmlenc ( message . text ) +
'</div></div><div class="clear"/>' ) ;
// show attachments
attachments ( message . files , '#id' + m . id + ' .text' , m . user , new Date ( m . time ) ) ;
// calculate and show emoticons
$ ( '#id' + m . id ) . emoticonize ( ) ;
if ( ! internal ) beep ( m . user ) ;
} )
. catch ( function ( e ) {
// not for me
success ( ) ;
} ) ;
}
/// Send Message To Server
/ * * U s e r w a n t s t o s e n d a m e s s a g e . E n c r y p t m e s s a g e w i t h o w n p r i v a t e a n d
the receiver ' s public key , then send it to the server . * /
function sendmessage ( recv , txt ) {
notice ( "1/3 preparing message …" ) ;
$ ( "#message" ) . prop ( ":disabled" , true ) ;
getPublicKey ( recv ) // get receiver's public key
. done ( function ( pk ) {
var key = openpgp . key . readArmored ( pk ) ;
if ( ! pk || key . err ) {
$ ( "#message" ) . prop ( ":disabled" , false ) ;
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 ( { receiver : recv , 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 …" ) ;
emit ( "message" , { user : userid ( ) , content : msg } ) ;
clearmessage ( ) ;
} )
. catch ( function ( e ) {
$ ( "#message" ) . prop ( ":disabled" , false ) ;
error ( "encryption of message failed" , true ) ;
} ) ;
} )
. fail ( function ( e ) {
$ ( "#message" ) . prop ( ":disabled" , false ) ;
error ( "user not found" , true ) ;
} ) ;
}
/// Check And Set Password
/ * * C h e c k i f g i v e n p a s s w o r d m a t c h e s t o d e c r y p t t h e p r i v a t e k e y . I f s o ,
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 ) ) {
success ( "password matches" ) ;
$ ( "#removeKey" ) . hide ( ) ;
password = pwd ;
chat ( ) ;
} else {
notice ( "password does not match" ) ;
}
}
/// Create Password Entry Field
/ * * A s k s u s e r f o r p a s s w o r d . W h e n u s e r s t a r t s t o e n t e r i t , i t i s
permanentely checked in setpw ( ) . As soon as the password matches ,
setpw ( ) continues automatically . No submit is required by the
user . * /
function getpwd ( ) {
if ( password ) return ;
$ ( "#removeKey" ) . show ( ) ;
status ( "getpwd" ) ;
}
function deleteUser ( ) {
var uid = userid ( ) ;
localStorage . removeItem ( pubkey ) ;
localStorage . removeItem ( privkey ) ;
error ( "user " + uid + " permanentely lost" ) ;
}
function removeKey ( ) {
togglemenu ( ) ;
$ ( "#removeKey" ) . hide ( ) ;
status ( 'forgotpassword' ) ;
}
/// Main Chat Window
/ * * G e t s c h a t w i d g e t s f r o m s e r v e r a n d d i s p l a y s t h e m . S t a r t s t i m e r f o r
get ( ) which polls for new messages . * /
var firsttime = true ;
function chat ( ) {
if ( ! password ) return getpwd ( ) ;
status ( "chat" ) ;
if ( firsttime && $ ( '#msgs' ) . is ( ':empty' ) ) {
firsttime = false ;
notice ( "getting previous messages, please wait …" ) ;
emit ( "messages" ) ;
}
}
/// Login User
/ * * T h i s i s n o t r e a l l y a l o g i n , i t i s j u s t s o m e k i n d o f v a l i d a t i o n .
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 ( ) {
$ ( "#username" ) . html ( userid ( ) + "@" + window . location . hostname ) ;
emit ( "login" , { name : userid ( ) ,
pubkey : localStorage . pubkey } ) ;
success ( "login sent to server" ) ;
}
/// Get And Display Form To Create New User
/ * * S h o w s u s e r c r e a t i o n f o r m . O n s u b m i t , a p r i v a t e k e y i s g e n e r a t e d i n
createkeypair ( ) , then login ( ) creates the user . * /
function newuser ( ) {
status ( "newuser" ) ;
}
/// Check if local storage is available
function checkLocalStorage ( ) {
var test = 'test' ;
try {
localStorage . setItem ( test , test ) ;
localStorage . removeItem ( test ) ;
return true ;
} catch ( e ) {
status ( "nolocalstorage" ) ;
error ( "local storage not available" ) ;
}
return false ;
}
/// Initial Function: Startup
/** Decide whether to login or to create a new user */
function start ( ) {
$ ( "#menu" ) . hide ( ) ;
//status("startup");
if ( checkLocalStorage ( ) )
try {
if ( ! userid ( ) ) {
newuser ( ) ;
} else {
login ( ) ;
}
} catch ( m ) {
console . log ( m . stack ) ;
error ( m ) ;
}
}
function init ( ) {
/// On Load, Call @ref start
$ ( window . onbeforeunload = function ( ) {
return "Are you sure you want to navigate away?" ;
} ) ;
/// Allow Running in Background on Android
document . addEventListener ( 'deviceready' , function ( ) {
if ( cordova && cordova . plugins . backgroundMode ) {
cordova . plugins . backgroundMode . enable ( ) ;
}
} , false ) ;
socket . io . on ( "connect" , connected ) ;
socket . io . on ( "reconnect" , connected ) ;
socket . io . on ( "disconnect" , disconnected ) ;
socket . io . on ( "error" , disconnected ) ;
socket . on ( "login" , loggedin ) ;
socket . on ( "fail" , fail ) ;
socket . on ( "user" , user ) ;
socket . on ( "users" , users ) ;
socket . on ( "message" , message ) ;
socket . on ( "messages" , messages ) ;
connectionstatus ( ) ;
if ( openpgp . initWorker ( "javascripts/openpgp.worker.js" ) )
console . log ( "asynchronous openpgp enabled" ) ;
else
console . log ( "asynchronous openpgp failed" ) ;
emit ( 'users' ) ;
start ( ) ;
}
/// Start Main Loop
$ ( init ) ;