@ -6,62 +6,16 @@ define([
'/bower_components/marked/marked.min.js' ,
'/bower_components/marked/marked.min.js' ,
'/common/common-realtime.js' ,
'/common/common-realtime.js' ,
// displayAvatar
// whenRealtimeSyncs
// getRealtime -> removeFromFriendList
/ * U I
Messages
confirm
fixHTML
displayAvatar
clearOwnedChannel
alert
pushMsg
removeFromFriendList
onDirectMessage
getNetwork
getProxy
pushMsg
Init
getNetwork
getProxy
onDirectMessage
removeFromFriendList
notify
onMessage
* /
] , function ( $ , Crypto , Curve , Hash , Marked , Realtime ) {
] , function ( $ , Crypto , Curve , Hash , Marked , Realtime ) {
var Msg = {
var Msg = {
inputs : [ ] ,
inputs : [ ] ,
} ;
} ;
var Types = {
message : 'MSG' ,
update : 'UPDATE' ,
unfriend : 'UNFRIEND' ,
mapId : 'MAP_ID' ,
mapIdAck : 'MAP_ID_ACK'
} ;
// TODO
// TODO
// - mute a channel (hide notifications or don't open it?)
// - mute a channel (hide notifications or don't open it?)
var ready = [ ] ;
var pending = { } ;
var pending = { } ;
var pendingRequests = [ ] ;
var pendingRequests = [ ] ;
var parseMessage = function ( content ) {
return Marked ( content ) ;
} ;
var createData = Msg . createData = function ( proxy , hash ) {
var createData = Msg . createData = function ( proxy , hash ) {
return {
return {
channel : hash || Hash . createChannelId ( ) ,
channel : hash || Hash . createChannelId ( ) ,
@ -82,13 +36,6 @@ define([
return proxy . friends ? proxy . friends [ pubkey ] : undefined ;
return proxy . friends ? proxy . friends [ pubkey ] : undefined ;
} ;
} ;
var removeFromFriendList = function ( proxy , realtime , curvePublic , cb ) {
if ( ! proxy . friends ) { return ; }
var friends = proxy . friends ;
delete friends [ curvePublic ] ;
Realtime . whenRealtimeSyncs ( realtime , cb ) ;
} ;
var getFriendList = Msg . getFriendList = function ( proxy ) {
var getFriendList = Msg . getFriendList = function ( proxy ) {
if ( ! proxy . friends ) { proxy . friends = { } ; }
if ( ! proxy . friends ) { proxy . friends = { } ; }
return proxy . friends ;
return proxy . friends ;
@ -101,7 +48,6 @@ define([
} ) ;
} ) ;
} ;
} ;
Msg . getFriendChannelsList = function ( proxy ) {
Msg . getFriendChannelsList = function ( proxy ) {
var list = [ ] ;
var list = [ ] ;
eachFriend ( proxy , function ( friend ) {
eachFriend ( proxy , function ( friend ) {
@ -110,500 +56,9 @@ define([
return list ;
return list ;
} ;
} ;
// Messaging tools
var avatars = { } ;
// TODO make this internal to the messenger
// TODO make this internal to the messenger
var channels = Msg . channels = window . channels = { } ;
var channels = Msg . channels = window . channels = { } ;
var UI = Msg . UI = { } ;
UI . init = function ( common , $listContainer , $msgContainer ) {
var ui = {
containers : {
friendList : $listContainer ,
messages : $msgContainer ,
} ,
} ;
ui . addToFriendList = function ( data , display , remove ) {
var $block = ui . containers . friendBlock ;
var $friend = $ ( '<div>' , { 'class' : 'friend avatar' } ) . appendTo ( $block ) ;
$friend . data ( 'key' , data . curvePublic ) ;
var $rightCol = $ ( '<span>' , { 'class' : 'right-col' } ) ;
$ ( '<span>' , { 'class' : 'name' } ) . text ( data . displayName ) . appendTo ( $rightCol ) ;
var $remove = $ ( '<span>' , { 'class' : 'remove fa fa-user-times' } ) . appendTo ( $rightCol ) ;
$remove . attr ( 'title' , common . Messages . contacts _remove ) ;
$friend . dblclick ( function ( ) {
if ( data . profile ) {
window . open ( '/profile/#' + data . profile ) ;
}
} ) ;
$friend . click ( function ( ) {
display ( data . curvePublic ) ;
} ) ;
$remove . click ( function ( e ) {
e . stopPropagation ( ) ;
common . confirm ( common . Messages . _getKey ( 'contacts_confirmRemove' , [
common . fixHTML ( data . displayName )
] ) , function ( yes ) {
if ( ! yes ) { return ; }
remove ( data . curvePublic ) ;
} , null , true ) ;
} ) ;
if ( data . avatar && avatars [ data . avatar ] ) {
$friend . append ( avatars [ data . avatar ] ) ;
$friend . append ( $rightCol ) ;
} else {
common . displayAvatar ( $friend , data . avatar , data . displayName , function ( $img ) {
if ( data . avatar && $img ) {
avatars [ data . avatar ] = $img [ 0 ] . outerHTML ;
}
$friend . append ( $rightCol ) ;
} ) ;
}
$ ( '<span>' , { 'class' : 'status' } ) . appendTo ( $friend ) ;
} ;
ui . createFriendList = function ( friends , display , remove ) {
var $block = ui . containers . friendBlock = $ ( '<div>' ) ;
eachFriend ( friends , function ( friend ) {
ui . addToFriendList ( friend , display , remove ) ;
} ) ;
$block . appendTo ( $listContainer ) ;
} ;
ui . notify = function ( curvePublic ) {
var $friend = $listContainer . find ( '.friend' ) . filter ( function ( idx , el ) {
return $ ( el ) . data ( 'key' ) === curvePublic ;
} ) ;
$friend . addClass ( 'notify' ) ;
} ;
ui . unnotify = function ( curvePublic ) {
var $friend = $listContainer . find ( '.friend' ) . filter ( function ( idx , el ) {
return $ ( el ) . data ( 'key' ) === curvePublic ;
} ) ;
$friend . removeClass ( 'notify' ) ;
} ;
ui . update = function ( curvePublic , types ) {
var proxy = common . getProxy ( ) ;
var data = getFriend ( proxy , curvePublic ) ;
var chan = channels [ data . channel ] ;
if ( ! chan . ready ) {
chan . updateOnReady = ( chan . updateOnReady || [ ] ) . concat ( types ) ;
return ;
}
var $friend = $listContainer . find ( '.friend' ) . filter ( function ( idx , el ) {
return $ ( el ) . data ( 'key' ) === curvePublic ;
} ) ;
if ( types . indexOf ( 'displayName' ) >= 0 ) {
$friend . find ( '.name' ) . text ( data . displayName ) ;
}
if ( types . indexOf ( 'avatar' ) >= 0 ) {
$friend . find ( '.default' ) . remove ( ) ;
$friend . find ( 'media-tag' ) . remove ( ) ;
if ( data . avatar && avatars [ data . avatar ] ) {
$friend . prepend ( avatars [ data . avatar ] ) ;
} else {
common . displayAvatar ( $friend , data . avatar , data . displayName , function ( $img ) {
if ( data . avatar && $img ) {
avatars [ data . avatar ] = $img [ 0 ] . outerHTML ;
}
} ) ;
}
}
} ;
ui . updateStatus = function ( curvePublic , online ) {
ui . getFriend ( curvePublic ) . find ( '.status' )
. attr ( 'class' , 'status ' + ( online ? 'online' : 'offline' ) ) ;
} ;
ui . getChannel = function ( curvePublic ) {
var $chat = $msgContainer . find ( '.chat' ) . filter ( function ( idx , el ) {
return $ ( el ) . data ( 'key' ) === curvePublic ;
} ) ;
return $chat . length ? $chat : null ;
} ;
ui . hideInfo = function ( ) {
$msgContainer . find ( '.info' ) . hide ( ) ;
} ;
ui . showInfo = function ( ) {
$msgContainer . find ( '.info' ) . show ( ) ;
} ;
ui . createChat = function ( curvePublic ) {
return $ ( '<div>' , { 'class' : 'chat' } )
. data ( 'key' , curvePublic ) . appendTo ( $msgContainer ) ;
} ;
ui . hideChat = function ( ) {
$msgContainer . find ( '.chat' ) . hide ( ) ;
} ;
ui . getFriend = function ( curvePublic ) {
return $listContainer . find ( '.friend' ) . filter ( function ( idx , el ) {
return $ ( el ) . data ( 'key' ) === curvePublic ;
} ) ;
} ;
ui . remove = function ( curvePublic ) {
var $friend = ui . getFriend ( curvePublic ) ;
var $chat = ui . getChannel ( curvePublic ) ;
$friend . remove ( ) ;
if ( $chat ) { $chat . remove ( ) ; }
ui . showInfo ( ) ;
} ;
ui . createMessage = function ( msg , name ) {
var $msg = $ ( '<div>' , { 'class' : 'message' } )
. attr ( 'title' , msg . time ? new Date ( msg . time ) . toLocaleString ( ) : '?' ) ;
if ( name ) {
$ ( '<div>' , { 'class' : 'sender' } ) . text ( name ) . appendTo ( $msg ) ;
}
$ ( '<div>' , { 'class' : 'content' } ) . html ( parseMessage ( msg . text ) ) . appendTo ( $msg ) ;
return $msg ;
} ;
ui . setEditable = function ( bool ) {
bool = ! bool ;
var input = ui . input ;
if ( ! input ) { return ; }
if ( bool ) {
input . setAttribute ( 'disabled' , bool ) ;
} else {
input . removeAttribute ( 'disabled' ) ;
}
if ( common . Messages ) {
// set placeholder
var placeholder = bool ?
common . Messages . disconnected :
common . Messages . contacts _typeHere ;
input . setAttribute ( 'placeholder' , placeholder ) ;
}
} ;
ui . createChatBox = function ( proxy , $container , curvePublic ) {
var data = getFriend ( proxy , curvePublic ) ;
// Input
var channel = channels [ data . channel ] ;
var $header = $ ( '<div>' , {
'class' : 'header' ,
} ) . appendTo ( $container ) ;
var $avatar = $ ( '<div>' , { 'class' : 'avatar' } ) . appendTo ( $header ) ;
// more history...
$ ( '<span>' , {
'class' : 'more-history' ,
} )
. text ( 'get more history' )
. click ( function ( ) {
console . log ( "GETTING HISTORY" ) ;
channel . getPreviousMessages ( ) ;
} )
. appendTo ( $header ) ;
var $removeHistory = $ ( '<span>' , {
'class' : 'remove-history fa fa-eraser' ,
title : common . Messages . contacts _removeHistoryTitle
} )
. click ( function ( ) {
common . confirm ( common . Messages . contacts _confirmRemoveHistory , function ( yes ) {
if ( ! yes ) { return ; }
common . clearOwnedChannel ( data . channel , function ( e ) {
if ( e ) {
console . error ( e ) ;
common . alert ( common . Messages . contacts _removeHistoryServerError ) ;
return ;
}
} ) ;
} ) ;
} ) ;
$removeHistory . appendTo ( $header ) ;
$ ( '<div>' , { 'class' : 'messages' } ) . appendTo ( $container ) ;
var $inputBlock = $ ( '<div>' , { 'class' : 'input' } ) . appendTo ( $container ) ;
var $input = $ ( '<textarea>' ) . appendTo ( $inputBlock ) ;
$input . attr ( 'placeholder' , common . Messages . contacts _typeHere ) ;
ui . input = $input [ 0 ] ;
var send = function ( ) {
// TODO implement sending queue
// TODO separate message logic from UI
var channel = channels [ data . channel ] ;
if ( channel . sending ) {
console . error ( "still sending" ) ;
return ;
}
if ( ! $input . val ( ) ) {
console . error ( "nothing to send" ) ;
return ;
}
if ( $input . attr ( 'disabled' ) ) {
console . error ( "input is disabled" ) ;
return ;
}
var payload = $input . val ( ) ;
// Send the message
channel . sending = true ;
channel . send ( payload , function ( e ) {
if ( e ) {
channel . sending = false ;
console . error ( e ) ;
return ;
}
$input . val ( '' ) ;
channel . refresh ( ) ;
channel . sending = false ;
} ) ;
} ;
$ ( '<button>' , {
'class' : 'btn btn-primary fa fa-paper-plane' ,
title : common . Messages . contacts _send ,
} ) . appendTo ( $inputBlock ) . click ( send ) ;
var onKeyDown = function ( e ) {
if ( e . keyCode === 13 ) {
if ( e . ctrlKey || e . shiftKey ) {
var val = this . value ;
if ( typeof this . selectionStart === "number" && typeof this . selectionEnd === "number" ) {
var start = this . selectionStart ;
this . value = val . slice ( 0 , start ) + "\n" + val . slice ( this . selectionEnd ) ;
this . selectionStart = this . selectionEnd = start + 1 ;
} else if ( document . selection && document . selection . createRange ) {
this . focus ( ) ;
var range = document . selection . createRange ( ) ;
range . text = "\r\n" ;
range . collapse ( false ) ;
range . select ( ) ;
}
return false ;
}
send ( ) ;
return false ;
}
} ;
$input . on ( 'keydown' , onKeyDown ) ;
// Header
var $rightCol = $ ( '<span>' , { 'class' : 'right-col' } ) ;
$ ( '<span>' , { 'class' : 'name' } ) . text ( data . displayName ) . appendTo ( $rightCol ) ;
if ( data . avatar && avatars [ data . avatar ] ) {
$avatar . append ( avatars [ data . avatar ] ) ;
$avatar . append ( $rightCol ) ;
} else {
common . displayAvatar ( $avatar , data . avatar , data . displayName , function ( $img ) {
if ( data . avatar && $img ) {
avatars [ data . avatar ] = $img [ 0 ] . outerHTML ;
}
$avatar . append ( $rightCol ) ;
} ) ;
}
} ;
return ui ;
} ;
var msgAlreadyKnown = function ( channel , sig ) {
return channel . messages . some ( function ( message ) {
return message [ 0 ] === sig ;
} ) ;
} ;
// TODO remove dependency on common
var pushMsg = function ( realtime , proxy , common , channel , cryptMsg ) {
var msg = channel . encryptor . decrypt ( cryptMsg ) ;
var sig = cryptMsg . slice ( 0 , 64 ) ;
if ( msgAlreadyKnown ( channel , sig ) ) { return ; }
var parsedMsg = JSON . parse ( msg ) ;
if ( parsedMsg [ 0 ] === Types . message ) {
// TODO validate messages here
var res = {
type : parsedMsg [ 0 ] ,
sig : sig ,
channel : parsedMsg [ 1 ] ,
time : parsedMsg [ 2 ] ,
text : parsedMsg [ 3 ] ,
} ;
channel . messages . push ( res ) ;
return true ;
}
if ( parsedMsg [ 0 ] === Types . update ) {
if ( parsedMsg [ 1 ] === proxy . curvePublic ) { return ; }
var newdata = parsedMsg [ 3 ] ;
var data = getFriend ( proxy , parsedMsg [ 1 ] ) ;
var types = [ ] ;
Object . keys ( newdata ) . forEach ( function ( k ) {
if ( data [ k ] !== newdata [ k ] ) {
types . push ( k ) ;
data [ k ] = newdata [ k ] ;
}
} ) ;
channel . updateUI ( types ) ;
return ;
}
if ( parsedMsg [ 0 ] === Types . unfriend ) {
removeFromFriendList ( proxy , realtime , channel . friendEd , function ( ) {
channel . wc . leave ( Types . unfriend ) ;
channel . removeUI ( ) ;
} ) ;
return ;
}
} ;
/ * B r o a d c a s t a d i s p l a y n a m e , p r o f i l e , o r a v a t a r c h a n g e t o a l l c o n t a c t s
* /
var updateMyData = function ( proxy ) {
var friends = getFriendList ( proxy ) ;
var mySyncData = friends . me ;
var myData = createData ( proxy ) ;
if ( ! mySyncData || mySyncData . displayName !== myData . displayName
|| mySyncData . profile !== myData . profile
|| mySyncData . avatar !== myData . avatar ) {
delete myData . channel ;
Object . keys ( channels ) . forEach ( function ( chan ) {
var channel = channels [ chan ] ;
var msg = [ Types . update , myData . curvePublic , + new Date ( ) , myData ] ;
var msgStr = JSON . stringify ( msg ) ;
var cryptMsg = channel . encryptor . encrypt ( msgStr ) ;
channel . wc . bcast ( cryptMsg ) . then ( function ( ) {
channel . refresh ( ) ;
} , function ( err ) {
console . error ( err ) ;
} ) ;
} ) ;
friends . me = myData ;
}
} ;
var onChannelReady = function ( proxy , chanId ) {
if ( ready . indexOf ( chanId ) !== - 1 ) { return ; }
ready . push ( chanId ) ;
channels [ chanId ] . updateStatus ( ) ; // c'est quoi?
var friends = getFriendList ( proxy ) ;
if ( ready . length === Object . keys ( friends ) . length ) {
// All channels are ready
updateMyData ( proxy ) ;
}
return ready . length ;
} ;
// Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function ( proxy , network , msg , sender ) {
var channel ;
var isId = Object . keys ( channels ) . some ( function ( chanId ) {
if ( channels [ chanId ] . userList . indexOf ( sender ) !== - 1 ) {
channel = channels [ chanId ] ;
return true ;
}
} ) ;
if ( ! isId ) { return ; }
var decryptedMsg = channel . encryptor . decrypt ( msg ) ;
if ( decryptedMsg === null ) {
// console.error('unable to decrypt message');
// console.error('potentially meant for yourself');
// message failed to parse, meaning somebody sent it to you but
// encrypted it with the wrong key, or you're sending a message to
// yourself in a different tab.
return ;
}
if ( ! decryptedMsg ) {
console . error ( 'decrypted message was falsey but not null' ) ;
return ;
}
var parsed ;
try {
parsed = JSON . parse ( decryptedMsg ) ;
} catch ( e ) {
console . error ( decryptedMsg ) ;
return ;
}
if ( parsed [ 0 ] !== Types . mapId && parsed [ 0 ] !== Types . mapIdAck ) { return ; }
// check that the responding peer's encrypted netflux id matches
// the sender field. This is to prevent replay attacks.
if ( parsed [ 2 ] !== sender || ! parsed [ 1 ] ) { return ; }
channel . mapId [ sender ] = parsed [ 1 ] ;
channel . updateStatus ( ) ;
if ( parsed [ 0 ] !== Types . mapId ) { return ; } // Don't send your key if it's already an ACK
// Answer with your own key
var rMsg = [ Types . mapIdAck , proxy . curvePublic , channel . wc . myID ] ;
var rMsgStr = JSON . stringify ( rMsg ) ;
var cryptMsg = channel . encryptor . encrypt ( rMsgStr ) ;
network . sendto ( sender , cryptMsg ) ;
} ;
// HERE
var onDirectMessage = function ( common , msg , sender ) {
var proxy = common . getProxy ( ) ;
var network = common . getNetwork ( ) ;
var realtime = common . getRealtime ( ) ;
if ( sender !== Msg . hk ) { return void onIdMessage ( proxy , network , msg , sender ) ; }
var parsed = JSON . parse ( msg ) ;
if ( ( parsed . validateKey || parsed . owners ) && parsed . channel ) {
return ;
}
if ( parsed . state && parsed . state === 1 && parsed . channel ) {
if ( channels [ parsed . channel ] ) {
// parsed.channel is Ready
// TODO: call a function that shows that the channel is ready? (remove a spinner, ...)
// channel[parsed.channel].ready();
channels [ parsed . channel ] . ready = true ;
onChannelReady ( proxy , parsed . channel ) ;
var updateTypes = channels [ parsed . channel ] . updateOnReady ;
if ( updateTypes ) {
channels [ parsed . channel ] . updateUI ( updateTypes ) ;
}
}
return ;
}
var chan = parsed [ 3 ] ;
if ( ! chan || ! channels [ chan ] ) { return ; }
pushMsg ( realtime , proxy , common , channels [ chan ] , parsed [ 4 ] ) ;
channels [ chan ] . refresh ( ) ;
} ;
var onMessage = function ( common , msg , sender , chan ) {
if ( ! channels [ chan . id ] ) { return ; }
var realtime = common . getRealtime ( ) ;
var proxy = common . getProxy ( ) ;
var isMessage = pushMsg ( realtime , proxy , common , channels [ chan . id ] , msg ) ;
if ( isMessage ) {
// Don't notify for your own messages
if ( channels [ chan . id ] . wc . myID !== sender ) {
channels [ chan . id ] . notify ( ) ;
}
channels [ chan . id ] . refresh ( ) ;
}
} ;
Msg . getLatestMessages = function ( ) {
Msg . getLatestMessages = function ( ) {
Object . keys ( channels ) . forEach ( function ( id ) {
Object . keys ( channels ) . forEach ( function ( id ) {
if ( id === 'me' ) { return ; }
if ( id === 'me' ) { return ; }
@ -613,280 +68,6 @@ define([
} ) ;
} ) ;
} ;
} ;
var getMoreHistory = function ( network , chan , hash , count ) {
var msg = [ 'GET_HISTORY_RANGE' , chan . id , {
from : hash ,
count : count ,
}
] ;
network . sendto ( network . historyKeeper , JSON . stringify ( msg ) ) . then ( function ( ) {
} , function ( err ) {
throw new Error ( err ) ;
} ) ;
} ;
var getChannelMessagesSince = function ( network , proxy , chan , data , keys ) {
var cfg = {
validateKey : keys . validateKey ,
owners : [ proxy . edPublic , data . edPublic ] ,
lastKnownHash : data . lastKnownHash
} ;
var msg = [ 'GET_HISTORY' , chan . id , cfg ] ;
network . sendto ( network . historyKeeper , JSON . stringify ( msg ) )
. then ( $ . noop , function ( err ) {
throw new Error ( err ) ;
} ) ;
} ;
/ * T O D O r e m o v e d e p e n d e n c y o n c o m m o n
* /
Msg . init = function ( common , ui ) {
// declare common variables
var network = common . getNetwork ( ) ;
var proxy = common . getProxy ( ) ;
var realtime = common . getRealtime ( ) ;
Msg . hk = network . historyKeeper ;
var friends = getFriendList ( proxy ) ;
// listen for messages...
network . on ( 'message' , function ( msg , sender ) {
onDirectMessage ( common , msg , sender ) ;
} ) ;
// declare messenger and common methods
var messenger = {
ui : ui ,
} ;
messenger . setActive = function ( id ) {
// TODO validate id
messenger . active = id ;
} ;
// Refresh the active channel
// TODO extract into UI method
var refresh = function ( curvePublic ) {
if ( messenger . active !== curvePublic ) { return ; }
var data = friends [ curvePublic ] ;
if ( ! data ) { return ; }
var channel = channels [ data . channel ] ;
if ( ! channel ) { return ; }
var $chat = ui . getChannel ( curvePublic ) ;
if ( ! $chat ) { return ; }
// Add new messages
var messages = channel . messages ;
var $messages = $chat . find ( '.messages' ) ;
var msg , name ;
var last = typeof ( channel . lastDisplayed ) === 'number' ? channel . lastDisplayed : - 1 ;
for ( var i = last + 1 ; i < messages . length ; i ++ ) {
msg = messages [ i ] ;
name = ( msg . channel !== channel . lastSender ) ?
getFriend ( proxy , msg . channel ) . displayName : undefined ;
ui . createMessage ( msg , name ) . appendTo ( $messages ) ;
channel . lastSender = msg . channel ;
}
$messages . scrollTop ( $messages [ 0 ] . scrollHeight ) ;
channel . lastDisplayed = i - 1 ;
channel . unnotify ( ) ;
// return void channel.notify();
if ( messages . length > 10 ) {
var lastKnownMsg = messages [ messages . length - 11 ] ;
channel . setLastMessageRead ( lastKnownMsg . sig ) ;
}
} ;
// Display a new channel
// TODO extract into UI method
var display = function ( curvePublic ) {
ui . hideInfo ( ) ;
var $chat = ui . getChannel ( curvePublic ) ;
if ( ! $chat ) {
$chat = ui . createChat ( curvePublic ) ;
ui . createChatBox ( proxy , $chat , curvePublic ) ;
}
// Show the correct div
ui . hideChat ( ) ;
$chat . show ( ) ;
// TODO set this attr per-messenger
messenger . setActive ( curvePublic ) ;
// TODO don't mark messages as read unless you have displayed them
refresh ( curvePublic ) ;
} ;
// TODO take a callback
var remove = function ( curvePublic ) {
var data = getFriend ( proxy , curvePublic ) ;
var channel = channels [ data . channel ] ;
var msg = [ Types . unfriend , proxy . curvePublic , + new Date ( ) ] ;
var msgStr = JSON . stringify ( msg ) ;
var cryptMsg = channel . encryptor . encrypt ( msgStr ) ;
channel . wc . bcast ( cryptMsg ) . then ( function ( ) {
removeFromFriendList ( common , curvePublic , function ( ) {
channel . wc . leave ( Types . unfriend ) ;
channel . removeUI ( ) ;
} ) ;
} , function ( err ) {
console . error ( err ) ;
} ) ;
} ;
// Display friend list
ui . createFriendList ( friends , display , remove ) ;
// Open the channels
// TODO extract this into an external function
var openFriendChannel = function ( data , f ) {
var keys = Curve . deriveKeys ( data . curvePublic , proxy . curvePrivate ) ;
var encryptor = Curve . createEncryptor ( keys ) ;
network . join ( data . channel ) . then ( function ( chan ) {
var channel = channels [ data . channel ] = {
sending : false ,
friendEd : f ,
keys : keys ,
encryptor : encryptor ,
messages : [ ] ,
refresh : function ( ) { refresh ( data . curvePublic ) ; } ,
notify : function ( ) {
ui . notify ( data . curvePublic ) ;
common . notify ( ) ; // HERE
} ,
unnotify : function ( ) { ui . unnotify ( data . curvePublic ) ; } ,
removeUI : function ( ) { ui . remove ( data . curvePublic ) ; } ,
updateUI : function ( types ) { ui . update ( data . curvePublic , types ) ; } ,
updateStatus : function ( ) {
ui . updateStatus ( data . curvePublic ,
channel . getStatus ( data . curvePublic ) ) ;
} ,
setLastMessageRead : function ( hash ) {
data . lastKnownHash = hash ;
} ,
getLastMessageRead : function ( ) {
return data . lastKnownHash ;
} ,
isActive : function ( ) {
return data . curvePublic === messenger . active ;
} ,
getMessagesSinceDisconnect : function ( ) {
getChannelMessagesSince ( network , proxy , chan , data , keys ) ;
} ,
wc : chan ,
userList : [ ] ,
mapId : { } ,
getStatus : function ( curvePublic ) {
return channel . userList . some ( function ( nId ) {
return channel . mapId [ nId ] === curvePublic ;
} ) ;
} ,
getPreviousMessages : function ( ) {
var history = channel . messages ;
if ( ! history || ! history . length ) {
// TODO ask for default history?
return ;
}
var oldestMessage = history [ 0 ] ;
if ( ! oldestMessage ) {
return ; // nothing to fetch
}
var messageHash = oldestMessage [ 0 ] ;
getMoreHistory ( network , chan , messageHash , 10 ) ;
} ,
send : function ( payload , cb ) {
if ( ! network . webChannels . some ( function ( wc ) {
if ( wc . id === channel . wc . id ) { return true ; }
} ) ) {
return void cb ( 'NO_SUCH_CHANNEL' ) ;
}
var msg = [ Types . message , proxy . curvePublic , + new Date ( ) , payload ] ;
var msgStr = JSON . stringify ( msg ) ;
var cryptMsg = channel . encryptor . encrypt ( msgStr ) ;
channel . wc . bcast ( cryptMsg ) . then ( function ( ) {
pushMsg ( realtime , proxy , common , channel , cryptMsg ) ;
cb ( ) ;
} , function ( err ) {
cb ( err ) ;
} ) ;
} ,
} ;
chan . on ( 'message' , function ( msg , sender ) {
onMessage ( common , msg , sender , chan ) ;
} ) ;
var onJoining = function ( peer ) {
if ( peer === Msg . hk ) { return ; }
if ( channel . userList . indexOf ( peer ) !== - 1 ) { return ; }
channel . userList . push ( peer ) ;
var msg = [ Types . mapId , proxy . curvePublic , chan . myID ] ;
var msgStr = JSON . stringify ( msg ) ;
var cryptMsg = channel . encryptor . encrypt ( msgStr ) ;
network . sendto ( peer , cryptMsg ) ;
channel . updateStatus ( ) ;
} ;
chan . members . forEach ( function ( peer ) {
if ( peer === Msg . hk ) { return ; }
if ( channel . userList . indexOf ( peer ) !== - 1 ) { return ; }
channel . userList . push ( peer ) ;
} ) ;
chan . on ( 'join' , onJoining ) ;
chan . on ( 'leave' , function ( peer ) {
var i = channel . userList . indexOf ( peer ) ;
while ( i !== - 1 ) {
channel . userList . splice ( i , 1 ) ;
i = channel . userList . indexOf ( peer ) ;
}
channel . updateStatus ( ) ;
} ) ;
getChannelMessagesSince ( network , proxy , chan , data , keys ) ;
} , function ( err ) {
console . error ( err ) ;
} ) ;
} ;
messenger . cleanFriendChannels = function ( ) {
Object . keys ( channels ) . forEach ( function ( id ) {
delete channels [ id ] ;
} ) ;
} ;
var openFriendChannels = messenger . openFriendChannels = function ( ) {
eachFriend ( friends , openFriendChannel ) ;
} ;
messenger . setEditable = ui . setEditable ;
openFriendChannels ( ) ;
// TODO split loop innards into ui methods
var checkNewFriends = function ( ) {
eachFriend ( friends , function ( friend , id ) {
var $friend = ui . getFriend ( id ) ;
if ( ! $friend . length ) {
openFriendChannel ( friend , id ) ;
ui . addToFriendList ( friend , display , remove ) ;
}
} ) ;
} ;
common . onDisplayNameChanged ( function ( ) {
checkNewFriends ( ) ;
updateMyData ( proxy ) ;
} ) ;
return messenger ;
} ;
// Invitation
// Invitation
// FIXME there are too many functions with this name
// FIXME there are too many functions with this name
var addToFriendList = Msg . addToFriendList = function ( common , data , cb ) {
var addToFriendList = Msg . addToFriendList = function ( common , data , cb ) {
@ -898,9 +79,11 @@ define([
friends [ pubKey ] = data ;
friends [ pubKey ] = data ;
Realtime . whenRealtimeSyncs ( common .getRealtime ( ) , function ( ) {
Realtime . whenRealtimeSyncs ( common , common .getRealtime ( ) , function ( ) {
cb ( ) ;
cb ( ) ;
common . pinPads ( [ data . channel ] ) ;
common . pinPads ( [ data . channel ] , function ( e ) {
if ( e ) { console . error ( e ) ; }
} ) ;
} ) ;
} ) ;
common . changeDisplayName ( proxy [ common . displayNameKey ] ) ;
common . changeDisplayName ( proxy [ common . displayNameKey ] ) ;
} ;
} ;