', {'class':'sender'}).text(name).appendTo($msg);
}
channel.lastSender = msg[0];
// content
$('
', {'class':'content'}).text(msg[2]).appendTo($msg);
}
$messages.scrollTop($messages[0].scrollHeight);
channel.lastDisplayed = i-1;
channel.unnotify();
if (messages.length > 10) {
var lastKnownMsg = messages[messages.length - 11];
data.lastKnownHash = lastKnownMsg[0];
}
};
// Display a new channel
var display = function (edPublic) {
var isNew = false;
var $chat = $msgContainer.find('.chat').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
if (!$chat.length) {
$chat = $('
', {'class':'chat'}).data('key', edPublic).appendTo($msgContainer);
createChatBox(common, $chat, edPublic);
isNew = true;
}
// Show the correct div
$msgContainer.find('.chat').hide();
$chat.show();
Msg.active = edPublic;
refresh(edPublic);
};
// Display friend list
common.getFriendListUI(common, display).appendTo($listContainer);
// Notify on new messages
var notify = function (edPublic) {
if (Msg.active === edPublic) { return; }
var $friend = $listContainer.find('.friend').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
$friend.addClass('notify');
};
var unnotify = function (edPublic) {
var $friend = $listContainer.find('.friend').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
$friend.removeClass('notify');
};
// Open the channels
Object.keys(friends).forEach(function (f) {
var data = friends[f];
var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate);
var encryptor = Curve.createEncryptor(keys);
channels[data.channel] = {
keys: keys,
encryptor: encryptor,
messages: [],
refresh: function () { refresh(data.edPublic); },
notify: function () { notify(data.edPublic); },
unnotify: function () { unnotify(data.edPublic); }
};
network.join(data.channel).then(function (chan) {
channels[data.channel].wc = chan;
chan.on('message', function (msg, sender) {
onMessage(msg, sender, chan);
});
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);
});
}, function (err) {
console.error(err);
});
});
};
// Invitation
// Remove should be called from the friend app at the moment
// The other user will know it from the private channel ("REMOVE_FRIEND" message?)
Msg.removeFromFriendList = function (common, edPublic, cb) {
var proxy = common.getProxy();
if (!proxy.friends) {
return;
}
var friends = proxy.friends;
delete friends[edPublic];
common.whenRealtimeSyncs(common.getRealtime(), cb);
};
var addToFriendList = Msg.addToFriendList = function (common, data, cb) {
var proxy = common.getProxy();
if (!proxy.friends) {
proxy.friends = {};
}
var friends = proxy.friends;
var pubKey = data.edPublic;
if (pubKey === proxy.edPublic) { return void cb("E_MYKEY"); }
if (friends[pubKey]) { return void cb("E_EXISTS"); }
friends[pubKey] = data;
common.whenRealtimeSyncs(common.getRealtime(), function () {
common.pinPads([data.channel], cb);
});
common.changeDisplayName(proxy[common.displayNameKey]);
};
Msg.addDirectMessageHandler = function (common) {
var network = common.getNetwork();
if (!network) { return void console.error('Network not ready'); }
network.on('message', function (message, sender) {
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = common.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var decryptMsg = Crypto.decrypt(message, key);
// Parse
msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
msg = ["FRIEND_REQ_NOK", chan];
var existing = getFriend(common, msgData.edPublic);
if (existing) {
msg = ["FRIEND_REQ_OK", chan, createData(common, existing.channel)];
msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
return;
}
common.confirm("Accept friend?", function (yes) { // XXX
if (yes) {
pending[sender] = msgData;
msg = ["FRIEND_REQ_OK", chan, createData(common, msgData.channel)];
}
msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
});
return;
}
if (msg[0] === "FRIEND_REQ_OK") {
// XXX
addToFriendList(common, msgData, function (err) {
if (err) {
return void common.log('Error while adding that friend to the list');
}
common.log('Friend invite accepted.');
var msg = ["FRIEND_REQ_ACK", chan];
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
});
return;
}
if (msg[0] === "FRIEND_REQ_NOK") {
// XXX
common.log('Friend invite rejected');
return;
}
if (msg[0] === "FRIEND_REQ_ACK") {
// XXX
var data = pending[sender];
if (!data) { return; }
addToFriendList(common, data, function (err) {
if (err) {
return void common.log('Error while adding that friend to the list');
}
common.log('Friend added to the list.');
});
return;
}
// TODO: timeout ACK: warn the user
} catch (e) {
console.error("Cannot parse direct message", msg || message, "from", sender, e);
}
});
};
Msg.inviteFromUserlist = function (common, netfluxId) {
var network = common.getNetwork();
var parsed = common.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var myData = createData(common);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
network.sendto(netfluxId, msgStr);
};
return Msg;
});