@ -37,870 +37,1026 @@
// 1 2 3 4 5 6 7 8
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
function SafeChatClient ( success , notice , error ) {
function SafeChat ( ) {
/// Cache Client's Key from local Strorage
/// Create UID from a name by appending an E-Mail
var k = null ;
function uid ( name ) {
return name + ' <' + name + '@' + hostname + '>'
function browserSupported ( ) {
window . indexedDB = window . indexedDB || window . mozIndexedDB || window . webkitIndexedDB || window . msIndexedDB
window . IDBTransaction = window . IDBTransaction || window . webkitIDBTransaction || window . msIDBTransaction
window . IDBKeyRange = window . IDBKeyRange || window . webkitIDBKeyRange || window . msIDBKeyRange
return window . indexedDB && window . crypto . getRandomValues && Storage
}
}
/// Get User Key
/// @class Crypto cryptographic functions
/ * * @ i n t e r n a l k e y i s t c a c h e d i n k
/** @param view is of class SafeChat.View */
@ return key * /
function Crypto ( view ) {
function key ( ) {
if ( k ) return k
/// cache client's key from local strorage
if ( typeof localStorage . key == 'undefined' ) return null
var k = null
return k = openpgp . key . readArmored ( localStorage . key )
}
/// detect hosstname, default to safechat.ch
var hostname = window . location . hostname != 'localhost' ? window . location . hostname : 'safechat.ch'
/// get user key
/ * * @ i n t e r n a l k e y i s t c a c h e d i n 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 user ( ) {
if ( k || key ( ) ) return k . pub . keys [ 0 ] . getUserIds ( ) [ 0 ]
return null
}
/// create New User
function createuser ( user , email , pwd ) {
view . notice ( "generating keys" )
openpgp . generateKey ( {
numBits : 4096 ,
userIds : [ { name : user , email : email } ] ,
passphrase : pwd
} ) . then ( function ( keyPair ) {
view . success ( "keys generated" )
localStorage . key = keyPair . privateKeyArmored
k = keyPair . key
} ) . catch ( function ( e ) {
console . log ( e )
view . fatal ( "generating key pairs failed" )
} )
}
/// open private key with password
/** @return @c true if password matches */
function password ( pwd ) {
return ( k || keys ( ) ) && k . keys [ 0 ] . decrypt ( pwd )
}
/// Encrypt Message
function encrypt ( message , targetkeys , done , failed ) {
if ( ! k ) return false
openpgp . encrypt ( {
publicKeys : targetkeys . keys . concat ( k . keys ) ,
privateKeys : k ,
data : message ,
armor : false
} ) . then ( done ) . else ( failed )
return true
}
/// Decrypt Message
function decrypt ( message , sourcekeys , done , failed ) {
if ( ! k ) return false
openpgp . decrypt ( {
privateKeys : k . keys ,
publicKeys : sourcekeys . keys ,
message : message
} ) . then ( done ) . else ( failed )
return true
}
/// 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
/// database that stores in indexed db
function createuser ( user , email , pwd ) {
function DataBase ( ) {
notice ( "generating keys" )
openpgp . generateKey ( {
function user ( name , key ) {
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 ) {
/// manage local copy of users
return ( k || keys ( ) ) && k . keys [ 0 ] . decrypt ( pwd )
function Users ( ) {
var users = new Map ( )
function add ( usr ) {
if ( ! users [ usr . name ] )
users [ usr . name ] . valid = true
else
users [ usr . name ] . valid = users [ usr . name ] . valid && users [ usr . name ] . key == usr . key
users [ usr . name ] . key = usr . key
users [ usr . name ] . online = usr . online
}
}
}
/// Encrypt Message
/// manage local copy of messages
function encrypt ( targetkeys , message , done , failed ) {
function Messages ( ) {
if ( ! k ) return false
openpgp . encrypt ( {
var messages = { } ;
publicKeys : targets . keys . concat ( k . keys ) ,
privateKeys : k ,
function add ( ) {
data : message ,
}
armor : false } )
. then ( done )
. else ( failed )
return true
}
}
/// Decrypt Message
/// @class Communication client socket communication
function decrypt ( message ) {
/** @param view is of class SafeChat.View */
if ( ! k ) return false
function Communication ( controller ) {
var socket = io . connect ( )
function broadcast ( signal , data ) {
console . log ( "<=snd " + signal )
socket . broadcast . emit ( signal , data )
}
function emit ( signal , data ) {
console . log ( "<-snd " + signal )
socket . emit ( signal , data )
}
socket
. on ( "login" , controller . loggedin )
. on ( "fail" , controller . fail )
. on ( "user" , controller . user )
. on ( "users" , controller . users )
. on ( "message" , controller . message )
. on ( "messages" , controller . messages )
. io
. on ( "connect" , controller . connected )
. on ( "reconnect" , controller . connected )
. on ( "disconnect" , controller . disconnected )
. on ( "error" , controller . disconnected )
return true
}
}
}
var password = null ; ///< password, only stored temporary, until reload
/// @class View provides the glue to the GUI in the index.ejs file
var username = null ; ///< username, only used during registration
/ * * V i e w p r o v i d e s t h e f o l l o w i n g c a l l b a c k s :
var filecontent = new Array ( ) ; ///< temporary storage for attachments
- status updates :
var socket = io . connect ( ) ;
- @ c notice ( msg ) to display information
var hostname = window . location . hostname != 'localhost' ? window . location . hostname : 'safechat.ch' ;
- @ c warning ( msg )
- @ c error ( msg )
- @ c fatal ( msg ) * /
function View ( ) {
/// Padding for numbers in dates
var nexttimer = null
function pad ( n ) {
return n < 10 ? '0' + n : n
}
function uid ( name ) {
/// Padding for numbers in dates
return name + ' <' + name + '@' + hostname + '>' ;
function pad ( n ) {
}
return n < 10 ? '0' + n : n
}
/// escape text to show in html @see htmldec
function htmlenc ( html ) {
return $ ( '<div/>' ) . text ( html ) . html ( )
}
/// decode html encoded text @see htmlenc
function htmldec ( data ) {
return $ ( '<div/>' ) . html ( data ) . text ( )
}
/// Convert number of bytes to readable text
/// alert user accoustically or by vibration
function size ( num ) {
/** alert user, e.g. that a new message has arrived. */
if ( num > 0.6 * 1024 ) {
function beep ( ) {
if ( num > 0.6 * 1024 * 1024 ) {
if ( navigator . vibrate ) navigator . vibrate ( 1000 )
if ( num > 0.6 * 1024 * 1024 * 1024 ) {
( new Audio ( "sounds/beep.mp3" ) ) . play ( )
if ( num > 0.6 * 1024 * 1024 * 1024 * 1024 ) {
}
return Math . round ( num / 1024 / 1024 / 1024 / 1024 ) + "TB" ;
/// show fatal error
/ * * s o m e t h i n g c o m p l e t e l y f a i l e d , a b o r t
@ param msg * /
function fatal ( msg ) {
if ( nexttimer ) clearTimeout ( nexttimer )
if ( msg ) {
error ( msg )
$ ( '#fatal-msg' ) . html ( msg )
}
show ( 'fatal' )
}
/// show error messsage
/ * * s h o w s 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 next ( optional ) next function to call * /
function error ( data , next ) {
if ( nexttimer ) clearTimeout ( nexttimer )
$ ( "#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 {
} else {
return Math . round ( num / 1024 / 1024 / 1024 ) + "GB" ;
$ ( "#status" ) . html ( 'error' )
console . log ( "error: " + JSON . stringify ( data ) )
}
}
} else {
} else {
return Math . round ( num / 1024 / 1024 ) + "MB" ;
$ ( "#status" ) . html ( 'error' )
console . log ( "error" )
}
}
} else {
$ ( "#status" ) . show ( )
return Math . round ( num / 1024 ) + "kB" ;
if ( next ) nexttimer = setTimeout ( function ( ) {
nexttimer = null
next ( )
} , 5000 )
}
}
} else {
return num + "B" ;
}
}
var reboottimer = null ;
/// show notice messsage
/// Show error messsage
/ * * s h o w s 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 .
/ * * 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 text ( optional ) The data is a string . * /
@ param data ( optional ) The error can be a string or any structure .
function notice ( text ) {
Strings are shown to the user , structures are logged only .
$ ( "#status" ) . hide ( )
@ param stay ( optional ) If not given as @ c true , reloads page after 5 s . * /
$ ( "#status" ) . addClass ( "notice" )
function error ( data , stay ) {
$ ( "#status" ) . removeClass ( "error" )
$ ( "#status" ) . hide ( ) ;
$ ( "#status" ) . removeClass ( "success" )
$ ( "#status" ) . addClass ( "error" )
if ( text ) {
$ ( "#status" ) . removeClass ( "notice" )
$ ( "#status" ) . html ( text )
$ ( "#status" ) . removeClass ( "success" )
console . log ( "notice: " + text )
if ( data ) {
} else {
if ( typeof data == 'string' ) {
$ ( "#status" ) . html ( '' )
$ ( "#status" ) . html ( data ) ;
console . log ( "notice" )
console . log ( "error: " + data ) ;
}
} else {
$ ( "#status" ) . show ( )
$ ( "#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
/// show success 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 .
/ * * s h o w s 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 . * /
@ param text ( optional ) The data is a string . * /
function notice ( text ) {
function success ( text ) {
$ ( "#status" ) . hide ( )
$ ( "#status" ) . hide ( )
$ ( "#status" ) . addClass ( "notice " )
$ ( "#status" ) . addClass ( "success" )
$ ( "#status" ) . removeClass ( "error" )
$ ( "#status" ) . removeClass ( "error" )
$ ( "#status" ) . removeClass ( "success " )
$ ( "#status" ) . removeClass ( "notice" )
if ( text ) {
if ( text ) {
$ ( "#status" ) . html ( text ) ;
$ ( "#status" ) . html ( text )
console . log ( "notice : " + text ) ;
console . log ( "success: " + text )
} else {
} else {
$ ( "#status" ) . html ( '' ) ;
$ ( "#status" ) . html ( '' )
console . log ( "notice" ) ;
console . log ( "success" )
}
}
$ ( "#status" ) . show ( ) ;
$ ( "#status" ) . show ( )
}
}
/// Show notice messsage
/// show a specific screen given the element id
/ * * 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 .
/ * * @ p a r a m i d h t m l i d t o b e s h o w n .
@ param text ( optional ) The data is a string . * /
@ param msg ( optional ) the success message text * /
function success ( text ) {
function show ( id , msg ) {
$ ( "#status" ) . hide ( ) ;
console . log ( "state: " + id )
$ ( "#status" ) . addClass ( "success" )
if ( msg ) success ( msg ) else $ ( "#status" ) . hide ( )
$ ( "#status" ) . removeClass ( "error" )
$ ( "#main" ) . children ( ":not(#" + id + ")" ) . hide ( )
$ ( "#status" ) . removeClass ( "notice" )
$ ( "#main #" + id ) . show ( )
if ( text ) {
$ ( "#main #" + id + " form input:first-child" ) . focus ( )
$ ( "#status" ) . html ( text ) ;
}
console . log ( "success: " + text ) ;
} else {
$ ( "#status" ) . html ( '' ) ;
console . log ( "success" ) ;
}
$ ( "#status" ) . show ( ) ;
}
/// Show status message in the main screen area
/// show server connected status
/ * * @ p a r a m i d H T M L i d t o b e s h o w n .
function connected ( ) {
@ param msg The success message text * /
console . log ( "server connected" )
function status ( id , msg ) {
$ ( "#connectionstatus #bad" ) . hide ( )
console . log ( "state: " + id ) ;
$ ( "#connectionstatus #good" ) . show ( )
if ( msg ) success ( msg ) ; else $ ( "#status" ) . hide ( ) ;
success ( "server connected" )
$ ( "#main" ) . children ( ":not(#" + id + ")" ) . hide ( ) ;
}
$ ( "#main #" + id ) . show ( ) ;
$ ( "#main #" + id + " form input:first-child" ) . focus ( ) ;
}
function emit ( signal , data ) {
/// show server disconnected status
console . log ( "<-snd " + signal ) ;
function disconnected ( ) {
socket . emit ( signal , data ) ;
console . log ( "server disconnected" )
}
$ ( "#connectionstatus #good" ) . hide ( )
$ ( "#connectionstatus #bad" ) . show ( )
error ( "server disconnected" , true )
}
function connected ( ) {
/// toggle menu display
console . log ( "server connected" ) ;
function togglemenu ( ) {
$ ( "#connectionstatus #bad" ) . hide ( ) ;
$ ( "#menu" ) . toggle ( )
$ ( "#connectionstatus #good" ) . show ( ) ;
}
success ( "server connected" ) ;
}
function disconnected ( ) {
function checkFeature ( id , query ) {
console . log ( "server disconnected" ) ;
if ( query ) $ ( '#' + id + ':before' )
$ ( "#connectionstatus #good" ) . hide ( ) ;
. css ( 'color' , 'green' )
$ ( "#connectionstatus #bad" ) . show ( ) ;
. css ( 'content' , '✔' )
error ( "server disconnected" , true ) ;
else $ ( '#' + id + ':before' )
}
. css ( 'color' , 'red' )
. css ( 'content' , '✘' )
if ( query ) $ ( '#' + id )
. css ( 'color' , 'green' )
. css ( 'text-decoration' , 'line-through' )
else $ ( '#' + id )
. css ( 'color' , 'red' )
. css ( 'text-decoration' , 'none' )
}
function connectionstatus ( ) {
function checkFeatures ( ) {
if ( socket . connected ) connected ( ) ; else disconnected ( ) ;
$ ( 'ul.features' ) . css ( 'list-style-type' , 'none' )
}
checkFeature ( "localstorage" , Storage )
checkFeature ( "indexeddb" , window . indexedDB )
checkFeature ( "randomvalues" , window . crypto . getRandomValues )
checkFeature ( "vibrate" , navigator . vibrate )
checkFeature ( "filereader" , window . FileReader )
}
function htmlenc ( html ) {
function DataTransfer ( ) {
return $ ( '<div/>' ) . text ( html ) . html ( ) ;
}
var reboottimer = null
var data = new DataTransfer ( )
/// 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 ( ) + "@" + hostname + ".bak"
var clickEvent = new MouseEvent ( "click" , {
"view" : window ,
"bubbles" : true ,
"cancelable" : false
} )
download . dispatchEvent ( clickEvent )
togglemenu ( )
}
function htmldec ( data ) {
/// Upload Profile Backup
return $ ( '<div/>' ) . html ( data ) . text ( ) ;
function restore ( evt ) {
}
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" )
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 && reboot ) reboottimer = setTimeout ( function ( ) {
reboottimer = null
} , 2000 )
}
reader . readAsText ( file )
}
}
if ( ! window . FileReader ) {
$ ( 'restore-menu-item' ) . hide ( )
error ( "your browser does not support file upload" )
}
}
/// 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
/// @class Controller defines the programm flow
function togglemenu ( ) {
function Controller ( view ) {
$ ( "#menu" ) . toggle ( ) ;
}
/// Download Profile Backup
var db = new Database ( )
function backup ( ) {
var communication = new Communication ( this )
var download = document . createElement ( 'a' ) ;
var users = new Users ( )
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 fail ( msg ) {
function restore ( evt ) {
console . log ( 'rcv-> fail(' + msg + ')' )
if ( ! window . FileReader )
error ( msg )
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 loggedin ( ) {
/** … */
console . log ( "rcv-> login" )
function groups ( ) {
success ( "login successful" )
}
chat ( )
}
/// Check if password is set and matches the repeated password
function user ( usr ) {
/ * * 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
console . log ( "rcv-> user" )
feedback to the user .
if ( usr . exits ) users . add ( usr )
}
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" ) ;
}
function users ( ) {
}
console . log ( "rcv-> users" )
}
/// Checks if the receiver of a message exists on server.
function message ( msg ) {
/ * * 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
console . log ( "rcv-> message" )
button if the receiver of the message exists on the server . * /
}
function checkpartner ( user ) {
$ ( "#chat" ) . submit ( function ( event ) {
return false ;
} ) ;
emit ( "user" , uid ( user ) ) ;
}
/// Create Local Public-/Private-Key Pair
function messages ( msgs ) {
/** Called if user has not yet his keys, just generates a new key pair. */
console . log ( "rcv-> messages" )
function createkeypair ( user , pwd ) {
}
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
this . connected = view . connected
/** @return public key object */
this . reconnect = view . connected
function publicKey ( ) {
this . disconnect = view . disconnected
if ( typeof localStorage . pubkey == 'undefined' ) {
this . error = view . disconnected
if ( typeof localStorage . pubKey == 'undefined' ) {
return null ;
function login ( ) {
} else {
localStorage . pubkey = localStorage . pubKey ;
localStorage . removeItem ( pubKey ) ;
}
}
}
return openpgp . key . readArmored ( localStorage . pubkey ) ;
}
/// Get Own Private Key
function user ( usr ) {
/** @return private key object */
if ( usr . exists ) db . adduser
function privateKey ( ) {
if ( typeof localStorage . privkey == 'undefined' ) {
if ( typeof localStorage . privKey == 'undefined' ) {
return null ;
} else {
localStorage . privkey = localStorage . privKey ;
localStorage . removeItem ( privKey ) ;
}
}
function initBrowser ( ) {
window . indexedDB = window . indexedDB || window . mozIndexedDB || window . webkitIndexedDB || window . msIndexedDB
window . IDBTransaction = window . IDBTransaction || window . webkitIDBTransaction || window . msIDBTransaction
window . IDBKeyRange = window . IDBKeyRange || window . webkitIDBKeyRange || window . msIDBKeyRange
navigator . vibrate = navigator . vibrate || navigator . webkitVibrate || navigator . mozVibrate || navigator . msVibrate
return window . indexedDB && window . crypto . getRandomValues && Storage
}
function run ( ) {
}
function start ( ) {
view . reboot = run
var compatible = initBrowser ( )
view . checkFeatures ( )
if ( ! compatible )
view . fatal ( "your browser is not supported" )
else
run ( )
}
}
}
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 */
return new Controller ( new View ( ) )
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 + "@" + hostname + '.' + ext ;
}
}
/// Display Image Attachments
var filecontent = new Array ( ) ///< temporary storage for attachments
function attachments ( files , id , from , date ) {
var reboottimer = null
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 connectionstatus ( ) {
if ( socket . connected ) connected ( ) else disconnected ( )
}
function done ( ) {
/// Configure local groups
if ( recorder ) {
/** … */
recorder . stop ( ) ;
function groups ( ) {
recorder . recording ( function ( data ) {
previewfile ( data , "video/webm" ) ;
abort ( ) ;
} ) ;
}
}
}
function abort ( ) {
/// Check if password is set and matches the repeated password
if ( recorder ) {
/ * * 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
$ ( "#videorecorder" ) . hide ( ) ;
feedback to the user .
recorder . release ( ) ;
delete recorder ; recorder = null ;
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" )
}
}
}
}
/// Record Video from builtin camera
/// Checks if the receiver of a message exists on server.
function recordvideo ( ) {
/ * * 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
try {
button if the receiver of the message exists on the server . * /
abort ( ) ;
function checkpartner ( user ) {
$ ( "#videorecorder" ) . show ( ) ;
$ ( "#chat" ) . submit ( function ( event ) {
recorder = new MediaStreamRecorder ( {
return false
video : {
} )
width : { ideal : 180 } ,
emit ( "user" , uid ( user ) )
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 ) {
/// Create Local Public-/Private-Key Pair
if ( ! name ) name = guessfilename ( type ) ;
/** Called if user has not yet his keys, just generates a new key pair. */
if ( type . match ( '^image/' ) ) {
function createkeypair ( user , pwd ) {
var img = document . createElement ( "img" ) ;
notice ( "generating keys" )
img . onload = function ( ) { // resize image to maximum 400px
openpgp . generateKey ( {
var MAX = 400 ;
numBits : 4096 ,
var width = img . width ;
userIds : [ { name : user , email : user + '@' + hostname } ] ,
var height = img . height ;
passphrase : pwd
if ( width > MAX ) {
} ) . then ( function ( keyPair ) {
height *= MAX / width ;
success ( "keys generated" )
width = MAX ;
localStorage . pubkey = keyPair . publicKeyArmored
}
localStorage . privkey = keyPair . privateKeyArmored
if ( height > MAX ) {
login ( )
width *= MAX / height ;
} ) . catch ( function ( e ) {
height = MAX ;
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 )
}
}
var canvas = document . createElement ( "canvas" ) ;
}
canvas . width = width ;
return openpgp . key . readArmored ( localStorage . pubkey )
canvas . height = height ;
}
var ctx = canvas . getContext ( "2d" ) ;
ctx . drawImage ( img , 0 , 0 , width , height ) ;
/// Get Own Private Key
img . onload = function ( ) {
/** @return private key object */
filecontent . push ( { name : name , type : type , content : img . src } ) ;
function privateKey ( ) {
$ ( "#preview" ) . append ( img ) ;
if ( typeof localStorage . privkey == 'undefined' ) {
success ( 'image is ready to be sent' ) ;
if ( typeof localStorage . privKey == 'undefined' ) {
return null
} else {
localStorage . privkey = localStorage . privKey
localStorage . removeItem ( privKey )
}
}
img . src = canvas . toDataURL ( file . type ) ;
}
img . title = name + "\n" + size ( img . src . length ) ;
return openpgp . key . readArmored ( localStorage . privkey )
}
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
/// Get Own User Name
/ * * 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
/** Get user name as user id of first public key */
an image , it resizes the image to 400 px on the lager side .
function userid ( ) {
if ( ! publicKey ( ) ||
By now , only images are supported .
publicKey ( ) . keys . length < 1 ||
publicKey ( ) . keys [ 0 ] . getUserIds ( ) . length < 1 ) return null
Stores data in global variable @ ref filecontent . * /
return publicKey ( ) . keys [ 0 ] . getUserIds ( ) [ 0 ]
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
/// Clear Message Text And Attachments
/ * * 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
/** Does not remove the receiver's name */
message text field .
function clearmessage ( ) {
$ ( "#message" ) . prop ( ":disabled" , true )
filecontent = new Array ( )
$ ( '#preview' ) . empty ( )
$ ( "#msg" ) . val ( "" )
$ ( "#message" ) . prop ( ":disabled" , false )
}
@ param name The receiver ' s name . * /
function guessfilename ( mimetype , user , date ) {
function setreceiver ( name ) {
if ( ! user ) user = userid ( )
$ ( "#recv" ) . val ( name ) ;
if ( ! date ) date = new Date ( )
checkpartner ( name ) ;
var ext = mimetype . replace ( /.*\/(x-)?/i , "" )
$ ( "#msg" ) . focus ( ) ;
return pad ( date . getFullYear ( ) ) + pad ( date . getMonth ( ) + 1 ) + pad ( date . getDate ( ) )
}
+ "-" + ext + "-" + user + "@" + hostname + '.' + ext
}
var userMap = null ;
/// Display Image Attachments
function users ( userlist ) {
function attachments ( files , id , from , date ) {
console . log ( "rcv-> users" ) ;
if ( files ) files . forEach ( function ( file ) {
userMap = new Array ( ) ;
console . log ( file )
$ ( "#allusers" ) . empty ( ) ;
if ( ! file . name ) file . name = guessfilename ( file . type , from , date )
userlist . forEach ( function ( usr ) {
var a = document . createElement ( 'a' )
userMap [ usr . name ] = usr . pubkey ;
a . href = file . content
$ ( "#allusers" ) . append ( '<option value="' + htmlenc ( usr . name ) + '">' )
a . download = file . name
$ ( "#allusers" ) . hide ( ) ;
a . target = '_blank'
console . log ( " user: " + usr . name ) ;
if ( file . type . match ( '^image/' ) ) {
} ) ;
var img = document . createElement ( 'img' )
localStorage . userMap = JSON . stringify ( userMap ) ;
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 )
} )
}
function fail ( msg ) {
var recorder
console . log ( "rcv-> fail" ) ;
error ( msg ) ;
}
function loggedin ( ) {
function done ( ) {
console . log ( "rcv-> login" ) ;
if ( recorder ) {
success ( "login successful" ) ;
recorder . stop ( )
chat ( ) ;
recorder . recording ( function ( data ) {
}
previewfile ( data , "video/webm" )
abort ( )
} )
}
}
function user ( usr ) {
function abort ( ) {
if ( usr . exists ) console . log ( "rcv-> user(" + usr . name + ")" ) ;
if ( recorder ) {
else console . log ( "rcv-> user(" + usr . name + "): name is available" ) ;
$ ( "#videorecorder" ) . hide ( )
if ( $ ( "#newuser" ) . is ( ":visible" ) && usr . name == uid ( $ ( '#user' ) . val ( ) ) ) {
recorder . release ( )
// same username as in the create user form
delete recorder recorder = null
$ ( "#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 == uid ( $ ( "#recv" ) . val ( ) ) ) { // same username as in receiver
$ ( '#send' ) . prop ( "disabled" , ! usr . exists ) ;
/// Record Video from builtin camera
$ ( "label[for=send] img" ) . css ( "opacity" , usr . exists ? "1.0" : "0.4" ) ;
function recordvideo ( ) {
$ ( "label[for=send] img" ) . css ( "filter" , usr . exists ? "alpha(opacity=100)" : "alpha(opacity=40)" ) ;
try {
if ( usr . exists ) success ( "recipient exists" ) ;
abort ( )
else error ( "unknown recipient" , true ) ;
$ ( "#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 )
}
}
}
if ( userMap == null ) {
if ( localStorage . userMap ) {
function previewfile ( content , type , name ) {
userMap = JSON . parse ( localStorage . userMap ) ;
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 {
} else {
userMap = new Array ( ) ;
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 )
}
}
}
}
if ( usr . exists && usr . pubkey && userMap [ usr . name ] != usr . pubkey ) {
userMap [ usr . name ] = usr . pubkey ;
/// Upload Attachment
$ ( "#allusers" ) . append ( 'option value="' + htmlenc ( usr . name ) + '"' )
/ * * 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
localStorage . userMap = JSON . stringify ( userMap ) ;
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 )
}
}
}
}
function queryuser ( usr ) {
/// Sets Receiver's Name
console . log ( "query user: " + uid ( usr ) ) ;
/ * * 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
socket . emit ( "user" , uid ( usr ) ) ;
message text field .
}
/// Get a user's public key.
@ param name The receiver ' s name . * /
/** The first time, gets it from the server, later from the cache. */
function setreceiver ( name ) {
function getPublicKey ( user ) {
$ ( "#recv" ) . val ( name )
var deferredObject = $ . Deferred ( ) ;
checkpartner ( name )
if ( userMap && userMap [ user ] ) deferredObject . resolve ( userMap [ user ] ) ;
$ ( "#msg" ) . focus ( )
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
var userMap = null
function message ( m , internal ) {
function users ( userlist ) {
if ( ! internal ) console . log ( "rcv-> message(" + m . user + ")" ) ;
console . log ( "rcv-> users" )
if ( ! password || ! privateKey ( ) ) return ;
userMap = new Array ( )
var key = openpgp . key . readArmored ( m . pubkey ) ;
$ ( "#allusers" ) . empty ( )
if ( key . err ) return error ( "key of sender unreadable" , true ) ;
userlist . forEach ( function ( usr ) {
var message = openpgp . message . readArmored ( m . msg ) ;
userMap [ usr . name ] = usr . pubkey
var privkey = privateKey ( ) . keys [ 0 ] ;
$ ( "#allusers" ) . append ( '<option value="' + htmlenc ( usr . name ) + '">' )
if ( privkey . decrypt ( password ) ) // prepare own key
$ ( "#allusers" ) . hide ( )
openpgp . decrypt ( {
console . log ( " user: " + usr . name )
privateKeys : privkey ,
} )
publicKeys : key . keys ,
localStorage . userMap = JSON . stringify ( userMap )
message : 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
function user ( usr ) {
/ * * 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
if ( usr . exists ) console . log ( "rcv-> user(" + usr . name + ")" )
the receiver ' s public key , then send it to the server . * /
else console . log ( "rcv-> user(" + usr . name + "): name is available" )
function sendmessage ( recv , txt ) {
if ( $ ( "#newuser" ) . is ( ":visible" ) && usr . name == uid ( $ ( '#user' ) . val ( ) ) ) {
notice ( "1/3 preparing message …" ) ;
// same username as in the create user form
$ ( "#message" ) . prop ( ":disabled" , true ) ;
$ ( "#createuser" ) . prop ( "disabled" , usr . exists ) // todo: check password
getPublicKey ( recv ) // get receiver's public key
if ( ! usr . exists ) {
. done ( function ( pk ) {
username = usr . name
var key = openpgp . key . readArmored ( pk ) ;
success ( "user name " + usr . name + " is available" )
if ( ! pk || key . err ) {
} else {
$ ( "#message" ) . prop ( ":disabled" , false ) ;
username = null
error ( "receiver's key not found" , true ) ;
error ( "user name " + usr . name + " is in use" , true )
return ;
}
}
var privkey = privateKey ( ) . keys [ 0 ] ;
}
privkey . decrypt ( password ) ; // get own private key ready
if ( $ ( "#chat" ) . is ( ":visible" ) && usr . name == uid ( $ ( "#recv" ) . val ( ) ) ) { // same username as in receiver
var message = JSON . stringify ( { receiver : recv , text : txt , files : filecontent } ) ;
$ ( '#send' ) . prop ( "disabled" , ! usr . exists )
notice ( "2/3 encrypting message …" ) ;
$ ( "label[for=send] img" ) . css ( "opacity" , usr . exists ? "1.0" : "0.4" )
openpgp . encrypt ( { publicKeys : key . keys . concat ( publicKey ( ) . keys ) ,
$ ( "label[for=send] img" ) . css ( "filter" , usr . exists ? "alpha(opacity=100)" : "alpha(opacity=40)" )
privateKeys : privkey ,
if ( usr . exists ) success ( "recipient exists" )
data : message ,
else error ( "unknown recipient" , true )
armor : false } )
}
. then ( function ( msg ) { // message is encrypted
if ( userMap == null ) {
notice ( "3/3 sending message …" ) ;
if ( localStorage . userMap ) {
emit ( "message" , { user : userid ( ) , content : msg } ) ;
userMap = JSON . parse ( localStorage . userMap )
clearmessage ( ) ;
} else {
} )
userMap = new Array ( )
. catch ( function ( e ) {
}
$ ( "#message" ) . prop ( ":disabled" , false ) ;
}
error ( "encryption of message failed" , true ) ;
if ( usr . exists && usr . pubkey && userMap [ usr . name ] != usr . pubkey ) {
} ) ;
userMap [ usr . name ] = usr . pubkey
} )
$ ( "#allusers" ) . append ( 'option value="' + htmlenc ( usr . name ) + '"' )
. fail ( function ( e ) {
localStorage . userMap = JSON . stringify ( userMap )
$ ( "#message" ) . prop ( ":disabled" , false ) ;
}
error ( "user not found" , true ) ;
}
} ) ;
}
/// Check And Set Password
function queryuser ( usr ) {
/ * * 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 ,
console . log ( "query user: " + uid ( usr ) )
store it in global temporary variable @ ref password and start the
socket . emit ( "user" , uid ( usr ) )
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
/// Get a user's public key.
/ * * 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
/** The first time, gets it from the server, later from the cache. */
permanentely checked in setpw ( ) . As soon as the password matches ,
function getPublicKey ( user ) {
setpw ( ) continues automatically . No submit is required by the
var deferredObject = $ . Deferred ( )
user . * /
if ( userMap && userMap [ user ] ) deferredObject . resolve ( userMap [ user ] )
function getpwd ( ) {
else deferredObject . reject ( "unknown user" )
if ( password ) return ;
return deferredObject . promise ( )
$ ( "#removeKey" ) . show ( ) ;
}
status ( "getpwd" ) ;
}
function deleteUser ( ) {
/// Received a list of messages from server
var uid = userid ( ) ;
function messages ( msgs ) {
localStorage . removeItem ( pubkey ) ;
console . log ( "rcv-> messages(" + msgs . length + ")" )
localStorage . removeItem ( privkey ) ;
if ( ! password || ! privateKey ( ) )
error ( "user " + uid + " permanentely lost" ) ;
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" )
}
function removeKey ( ) {
/// Received a message from server
togglemenu ( ) ;
function message ( m , internal ) {
$ ( "#removeKey" ) . hide ( ) ;
if ( ! internal ) console . log ( "rcv-> message(" + m . user + ")" )
status ( 'forgotpassword' ) ;
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 . decrypt ( {
privateKeys : privkey ,
publicKeys : key . keys ,
message : message
} ) . then ( function ( msg ) { // decryption succeded
openpgp . decrypt ( {
privateKeys : privkey ,
publicKeys : key . keys ,
message : 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 ( )
} )
}
/// Main Chat Window
/// Send Message To Server
/ * * 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
/ * * 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
get ( ) which polls for new messages . * /
the receiver ' s public key , then send it to the server . * /
var firsttime = true ;
function sendmessage ( recv , txt ) {
function chat ( ) {
notice ( "1/3 preparing message …" )
if ( ! password ) return getpwd ( ) ;
$ ( "#message" ) . prop ( ":disabled" , true )
status ( "chat" ) ;
getPublicKey ( recv ) // get receiver's public key
if ( firsttime && $ ( '#msgs' ) . is ( ':empty' ) ) {
. done ( function ( pk ) {
firsttime = false ;
var key = openpgp . key . readArmored ( pk )
notice ( "getting previous messages, please wait …" ) ;
if ( ! pk || key . err ) {
emit ( "messages" ) ;
$ ( "#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 . encrypt ( { publicKeys : key . keys . concat ( publicKey ( ) . keys ) ,
privateKeys : privkey ,
data : message ,
armor : false } )
. 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 )
} )
}
/// Login User
/// Check And Set Password
/ * * 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 .
/ * * 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 ,
The server does not care if a user is online or not , it is only
store it in global temporary variable @ ref password and start the
interesting to the client to make sure , everything is fine . User
chat . The password matches , when the private key can be decrypted .
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
@ param pwd The password to check . * /
public key is the same , the user is considered logged in , his
function setpw ( pwd ) {
credentials seem to be valid . If user does not yet exits on
if ( privateKey ( ) . keys [ 0 ] . decrypt ( pwd ) ) {
server , it is created now . If user exists , but public key is
success ( "password matches" )
different , then this is a complete failure , something went
$ ( "#removeKey" ) . hide ( )
terribly wrong . * /
password = pwd
function login ( ) {
chat ( )
$ ( "#username" ) . html ( userid ( ) + "@" + hostname ) ;
} else {
emit ( "login" , { name : userid ( ) ,
notice ( "password does not match" )
pubkey : localStorage . pubkey } ) ;
}
success ( "login sent to server" ) ;
}
}
/// Get And Display Form To Create New User
/// Create Password Entry Field
/ * * 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
/ * * 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
createkeypair ( ) , then login ( ) creates the user . * /
permanentely checked in setpw ( ) . As soon as the password matches ,
function newuser ( ) {
setpw ( ) continues automatically . No submit is required by the
status ( "newuser" ) ;
user . * /
}
function getpwd ( ) {
if ( password ) return
$ ( "#removeKey" ) . show ( )
status ( "getpwd" )
}
/// Check if local storage is available
function deleteUser ( ) {
function checkLocalStorage ( ) {
var uid = userid ( )
var test = 'test' ;
localStorage . removeItem ( pubkey )
try {
localStorage . removeItem ( privkey )
localStorage . setItem ( test , test ) ;
error ( "user " + uid + " permanentely lost" )
localStorage . removeItem ( test ) ;
}
return true ;
} catch ( e ) {
status ( "nolocalstorage" ) ;
error ( "local storage not available" ) ;
}
return false ;
}
/// Initial Function: Startup
function removeKey ( ) {
/** Decide whether to login or to create a new user */
togglemenu ( )
function start ( ) {
$ ( "#removeKey" ) . hide ( )
$ ( "#menu" ) . hide ( ) ;
status ( 'forgotpassword' )
//status("startup");
}
if ( checkLocalStorage ( ) )
try {
if ( ! userid ( ) ) {
newuser ( ) ;
} else {
login ( ) ;
}
} catch ( m ) {
console . log ( m . stack ) ;
error ( m ) ;
}
}
function init ( ) {
/// Main Chat Window
/// On Load, Call @ref start
/ * * 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
$ ( window . onbeforeunload = function ( ) {
get ( ) which polls for new messages . * /
return "Are you sure you want to navigate away?" ;
var firsttime = true
} ) ;
function chat ( ) {
/// Allow Running in Background on Android
if ( ! password ) return getpwd ( )
document . addEventListener ( 'deviceready' , function ( ) {
status ( "chat" )
if ( cordova && cordova . plugins . backgroundMode ) {
if ( firsttime && $ ( '#msgs' ) . is ( ':empty' ) ) {
cordova . plugins . backgroundMode . enable ( ) ;
firsttime = false
}
notice ( "getting previous messages, please wait …" )
} , false ) ;
emit ( "messages" )
socket . io . on ( "connect" , connected ) ;
}
socket . io . on ( "reconnect" , connected ) ;
}
socket . io . on ( "disconnect" , disconnected ) ;
socket . io . on ( "error" , disconnected ) ;
/// Login User
socket . on ( "login" , loggedin ) ;
/ * * 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 .
socket . on ( "fail" , fail ) ;
The server does not care if a user is online or not , it is only
socket . on ( "user" , user ) ;
interesting to the client to make sure , everything is fine . User
socket . on ( "users" , users ) ;
is logged in the following way : User name and public key are sent
socket . on ( "message" , message ) ;
to the server . If the user name exists on the server and the
socket . on ( "messages" , messages ) ;
public key is the same , the user is considered logged in , his
connectionstatus ( ) ;
credentials seem to be valid . If user does not yet exits on
if ( openpgp . initWorker ( "openpgp.worker.min.js" ) )
server , it is created now . If user exists , but public key is
console . log ( "asynchronous openpgp enabled" ) ;
different , then this is a complete failure , something went
else
terribly wrong . * /
console . log ( "asynchronous openpgp failed" ) ;
function login ( ) {
emit ( 'users' ) ;
$ ( "#username" ) . html ( userid ( ) + "@" + hostname )
start ( ) ;
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 )
connectionstatus ( )
if ( openpgp . initWorker ( "openpgp.worker.min.js" ) )
console . log ( "asynchronous openpgp enabled" )
else
console . log ( "asynchronous openpgp failed" )
emit ( 'users' )
start ( )
}
/// Start Main Loop
/// Start Main Loop
$ ( init ) ;
$ ( init )