From 013b75ae6708f46f798d8379e7c93d272a83dae1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 24 Aug 2017 11:33:33 +0200 Subject: [PATCH] implement history range requests --- www/common/common-messenger.js | 203 +++++++++++++++++++++++++++------ www/contacts/main.less | 4 +- www/contacts2/main.js | 6 - www/contacts2/messenger-ui.js | 162 ++++++++++++++++++++------ 4 files changed, 299 insertions(+), 76 deletions(-) diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index d087294aa..d73e73c6e 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -82,33 +82,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); - }); - }; - getMoreHistory = getMoreHistory; // FIXME - - 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); - }); - }; - // Invitation // FIXME there are too many functions with this name var addToFriendList = Msg.addToFriendList = function (common, data, cb) { @@ -247,13 +220,20 @@ define([ }; Msg.messenger = function (common) { + 'use strict'; var messenger = { handlers: { message: [], join: [], leave: [], update: [], + new_friend: [], }, + range_requests: {}, + }; + + var eachHandler = function (type, g) { + messenger.handlers[type].forEach(g); }; messenger.on = function (type, f) { @@ -299,6 +279,38 @@ define([ return channels[chanId]; }; + var initRangeRequest = function (txid, curvePublic, sig, cb) { + messenger.range_requests[txid] = { + messages: [], + cb: cb, + curvePublic: curvePublic, + sig: sig, + }; + }; + + var getRangeRequest = function (txid) { + return messenger.range_requests[txid]; + }; + + messenger.getMoreHistory = function (curvePublic, hash, count, cb) { + if (typeof(cb) !== 'function') { return; } + var chan = getChannel(curvePublic); + var txid = common.uid(); + initRangeRequest(txid, curvePublic, hash, cb); + // FIXME hash is not necessarily defined. + var msg = [ 'GET_HISTORY_RANGE', chan.id, { + from: hash, + count: count, + txid: txid, + } + ]; + + network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function () { + }, function (err) { + throw new Error(err); + }); + }; + var getCurveForChannel = function (id) { var channel = channels[id]; if (!channel) { return; } @@ -372,6 +384,27 @@ define([ network.sendto(sender, cryptMsg); }; + var orderMessages = function (curvePublic, new_messages, sig) { + var channel = getChannel(curvePublic); + var messages = channel.messages; + var idx; + messages.some(function (msg, i) { + if (msg.sig === sig) { idx = i; } + return true; + }); + + if (typeof(idx) !== 'undefined') { + console.error('found old message at %s', idx); + } else { + console.error("did not find desired message"); + } + + // TODO improve performance + new_messages.reverse().forEach(function (msg) { + messages.unshift(msg); + }); + }; + var pushMsg = function (channel, cryptMsg) { var msg = channel.encryptor.decrypt(cryptMsg); @@ -447,7 +480,8 @@ define([ var msgStr = JSON.stringify(msg); var cryptMsg = channel.encryptor.encrypt(msgStr); channel.wc.bcast(cryptMsg).then(function () { - channel.refresh(); + // TODO send event + //channel.refresh(); }, function (err) { console.error(err); }); @@ -468,6 +502,56 @@ define([ var onDirectMessage = function (common, msg, sender) { if (sender !== Msg.hk) { return void onIdMessage(msg, sender); } var parsed = JSON.parse(msg); + + if (/HISTORY_RANGE/.test(parsed[0])) { + //console.log(parsed); + var txid = parsed[1]; + var req = getRangeRequest(txid); + var type = parsed[0]; + if (!req) { + return void console.error("received response to unknown request"); + } + + if (type === 'HISTORY_RANGE') { + //console.log(parsed); + req.messages.push(parsed[2]); // TODO use pushMsg instead + } else if (type === 'HISTORY_RANGE_END') { + // process all the messages (decrypt) + var curvePublic = req.curvePublic; + var channel = getChannel(curvePublic); + + var decrypted = req.messages.map(function (msg) { + if (msg[2] !== 'MSG') { return; } + try { + return { + d: JSON.parse(channel.encryptor.decrypt(msg[4])), + sig: msg[4].slice(0, 64), + }; + } catch (e) { + console.log('failed to decrypt'); + return null; + } + }).filter(function (decrypted) { + return decrypted; + }).map(function (O) { + return { + type: O.d[0], + sig: O.sig, + channel: O.d[1], + time: O.d[2], + text: O.d[3], + curve: curvePublic, + }; + }); + + orderMessages(curvePublic, decrypted, req.sig); + return void req.cb(void 0, decrypted); + } else { + console.log(parsed); + } + return; + } + if ((parsed.validateKey || parsed.owners) && parsed.channel) { return; } @@ -510,12 +594,14 @@ define([ }); messenger.removeFriend = function (curvePublic, cb) { - // TODO throw if no callback + if (typeof(cb) !== 'function') { throw new Error('NO_CALLBACK'); } 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); + + // TODO emit remove_friend event? channel.wc.bcast(cryptMsg).then(function () { delete friends[curvePublic]; Realtime.whenRealtimeSyncs(realtime, function () { @@ -527,8 +613,20 @@ define([ }); }; - // Open the channels - // TODO (curvePublic, cb) => (e) // READY + var getChannelMessagesSince = function (chan, data, keys) { + console.log('Fetching [%s] messages since [%s]', data.curvePublic, data.lastKnownHash || ''); + 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); + }); + }; + var openFriendChannel = function (data, f) { var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate); var encryptor = Curve.createEncryptor(keys); @@ -570,6 +668,8 @@ define([ var onJoining = function (peer) { if (peer === Msg.hk) { return; } if (channel.userList.indexOf(peer) !== -1) { return; } + + // FIXME this doesn't seem to be mapping correctly channel.userList.push(peer); var msg = [Types.mapId, proxy.curvePublic, chan.myID]; var msgStr = JSON.stringify(msg); @@ -584,7 +684,7 @@ define([ chan.on('join', onJoining); chan.on('leave', function (peer) { var curvePublic = channel.mapId[peer]; - console.log(curvePublic); + console.log(curvePublic); // FIXME var i = channel.userList.indexOf(peer); while (i !== -1) { @@ -598,7 +698,8 @@ define([ }); }); - getChannelMessagesSince(network, proxy, chan, data, keys); + // FIXME don't subscribe to the channel implicitly + getChannelMessagesSince(chan, data, keys); }, function (err) { console.error(err); }); @@ -633,10 +734,10 @@ define([ })); }; +/* messenger.openFriendChannels = function () { eachFriend(friends, openFriendChannel); - }; - + };*/ messenger.openFriendChannel = function (curvePublic, cb) { if (typeof(curvePublic) !== 'string') { return void cb('INVALID_ID'); } @@ -694,9 +795,39 @@ define([ messenger.getFriendInfo = function (curvePublic, cb) { var friend = friends[curvePublic]; if (!friend) { return void cb('NO_SUCH_FRIEND'); } - cb(void 0, friend); + // this clone will be redundant when ui uses postmessage + cb(void 0, clone(friend)); + }; + + messenger.getMyInfo = function (cb) { + cb(void 0, { + curvePublic: proxy.curvePublic, + displayName: common.getDisplayName(), + }); }; + // TODO listen for changes to your friend list + // emit 'update' events for clients + + //var update = function (curvePublic + proxy.on('change', ['friends'], function (o, n, p) { + var curvePublic; + if (o === undefined) { + // new friend added + curvePublic = p.slice(-1)[0]; + eachHandler('new_friend', function (f) { + f(clone(n), curvePublic); + }); + return; + } + + console.error(o, n, p); + }).on('remove', ['friends'], function (o, p) { + console.error(o, p); + }); + + Object.freeze(messenger); + return messenger; }; diff --git a/www/contacts/main.less b/www/contacts/main.less index d8d50a41f..b291328c2 100644 --- a/www/contacts/main.less +++ b/www/contacts/main.less @@ -170,8 +170,8 @@ body { margin: 10px; } .more-history { - display: none; - //.hover; + //display: none; + .hover; } } .chat { diff --git a/www/contacts2/main.js b/www/contacts2/main.js index 9e318dac9..664ffc333 100644 --- a/www/contacts2/main.js +++ b/www/contacts2/main.js @@ -62,14 +62,8 @@ define([ $('
  • ').text(Messages.contacts_info3).appendTo($ul); //$('
  • ').text(Messages.contacts_info4).appendTo($ul); - - //var ui = APP.ui = Cryptpad.initMessagingUI(Cryptpad, $list, $messages); - //APP.messenger = Cryptpad.initMessaging(Cryptpad, ui); - var messenger = window.messenger = Messenger.messenger(Cryptpad); UI.create(messenger, $list, $messages); - - Cryptpad.removeLoadingScreen(); }; Cryptpad.ready(function () { diff --git a/www/contacts2/messenger-ui.js b/www/contacts2/messenger-ui.js index 9f80f4ac6..fed13f108 100644 --- a/www/contacts2/messenger-ui.js +++ b/www/contacts2/messenger-ui.js @@ -10,18 +10,33 @@ define([ var UI = {}; var Messages = Cryptpad.Messages; - var stub = function (label) { - console.error('stub: ' + label); + var m = function (md) { + var d = h('div.content'); + d.innerHTML = Marked(md || ''); + return d; }; var dataQuery = function (curvePublic) { return '[data-key="' + curvePublic + '"]'; }; + var initChannel = function (state, curvePublic, info) { + console.log('initializing channel for [%s]', curvePublic); + //console.log(info); + state.channels[curvePublic] = { + messages: [], + HEAD: info.lastKnownHash, + }; + }; + UI.create = function (messenger, $userlist, $messages) { - var state = { + var state = window.state = { active: '', }; + + state.channels = {}; + var displayNames = state.displayNames = {}; + var avatars = state.avatars = {}; var setActive = function (curvePublic) { state.active = curvePublic; @@ -48,17 +63,40 @@ define([ title: msg.time? new Date(msg.time).toLocaleString(): '?', }, [ name? h('div.sender', name): undefined, - h('div.content', msg.text), + m(msg.text), ]); }; + var getChat = function (curvePublic) { + return $messages.find(dataQuery(curvePublic)); + }; + markup.chatbox = function (curvePublic, data) { var moreHistory = h('span.more-history', ['get more history']); // TODO translate var displayName = data.displayName; + var fetching = false; $(moreHistory).click(function () { - stub('get older history'); + //stub('get older history'); console.log('getting history'); + + // get oldest known message... + var channel = state.channels[curvePublic]; + var sig = !(channel.messages && channel.messages.length)? + channel.HEAD: channel.messages[0].sig; + + fetching = true; + var $messages = $(getChat(curvePublic)).find('.messages'); + messenger.getMoreHistory(curvePublic, sig, 10, function (e, history) { + fetching = false; + if (e) { return void console.error(e); } + history.forEach(function (msg) { + channel.messages.unshift(msg); + var name = displayNames[msg.channel]; + var el_message = markup.message(msg, name); + $messages.prepend(el_message); + }); + }); }); var removeHistory = h('span.remove-history.fa.fa-eraser', { @@ -110,6 +148,7 @@ define([ var sending = false; var send = function (content) { + if (typeof(content) !== 'string' || !content.trim()) { return; } if (sending) { return false; } sending = true; messenger.sendMessage(curvePublic, content, function (e) { @@ -170,14 +209,11 @@ define([ $messages.find('.info').hide(); }; - var getChat = function (curvePublic) { - return $messages.find(dataQuery(curvePublic)); - }; - var updateStatus = function (curvePublic) { var $status = find.inList(curvePublic).find('.status'); + // FIXME this stopped working :( messenger.getStatus(curvePublic, function (e, online) { - if (e) { return void console.error(e); } + if (e) { return void console.error(curvePublic, e); } if (online) { return void $status .removeClass('offline').addClass('online'); @@ -187,17 +223,34 @@ define([ }; var display = function (curvePublic) { + var channel = state.channels[curvePublic]; + var lastMsg = channel.messages.slice(-1)[0]; + + if (lastMsg) { + channel.HEAD = lastMsg.sig; + messenger.setChannelHead(curvePublic, channel.HEAD, function (e) { + if (e) { console.error(e); } + }); + } + setActive(curvePublic); unnotify(curvePublic); var $chat = getChat(curvePublic); hideInfo(); $messages.find('div.chat[data-key]').hide(); if ($chat.length) { + var $chat_messages = $chat.find('div.message'); + if (!$chat_messages.length) { + var $more = $chat.find('.more-history'); + console.log($more); + $more.click(); + } return void $chat.show(); } messenger.getFriendInfo(curvePublic, function (e, info) { if (e) { return void console.error(e); } // FIXME - $messages.append(markup.chatbox(curvePublic, info)); + var chatbox = markup.chatbox(curvePublic, info); + $messages.append(chatbox); }); }; @@ -208,9 +261,9 @@ define([ }); }; - var friendExistsInUserList = function (curvePublic) { +/* var friendExistsInUserList = function (curvePublic) { return !!$userlist.find(dataQuery(curvePublic)).length; - }; + }; */ markup.friend = function (data) { var curvePublic = data.curvePublic; @@ -241,8 +294,10 @@ define([ Cryptpad.fixHTML(data.displayName) ]), function (yes) { if (!yes) { return; } - stub('remove friend: ' + curvePublic); removeFriend(curvePublic); + // TODO remove friend from userlist ui + // FIXME seems to trigger EJOINED from netflux-websocket (from server); + // (tried to join a channel in which you were already present) }); }); @@ -261,32 +316,53 @@ define([ return $friend; }; - var displayNames = {}; + var initializing = true; + + // TODO handle scrolling. messenger.on('message', function (message) { console.log(JSON.stringify(message)); - Cryptpad.notify(); + if (!initializing) { Cryptpad.notify(); } var curvePublic = message.curve; - if (!isActive(curvePublic)) { notify(curvePublic); } - var name = displayNames[curvePublic]; var chat = getChat(curvePublic, name); var el_message = markup.message(message, name); + state.channels[curvePublic].messages.push(message); + var $chat = $(chat); - console.log(chat, $chat, el_message.outerHTML); $chat.find('.messages').append(el_message); + var channel = state.channels[curvePublic]; + if (!channel) { + console.error('expected channel [%s] to be open', curvePublic); + return; + } + + if (isActive(curvePublic)) { + channel.HEAD = message.sig; + messenger.setChannelHead(curvePublic, message.sig, function (e) { + if (e) { return void console.error(e); } + }); + return; + } + var lastMsg = channel.messages.slice(-1)[0]; + if (lastMsg.sig !== channel.HEAD) { + return void notify(curvePublic); + } + unnotify(curvePublic); // TODO notify if a message is newer than `lastKnownHash` }); messenger.on('join', function (curvePublic, channel) { - console.log('join', curvePublic, channel); + //console.log('join', curvePublic, channel); + channel = channel; updateStatus(curvePublic); }); messenger.on('leave', function (curvePublic, channel) { - console.log('leave', curvePublic, channel); + //console.log('leave', curvePublic, channel); + channel = channel; updateStatus(curvePublic); }); @@ -300,20 +376,42 @@ define([ messenger.updateMyData(); }); + // FIXME dirty hack + messenger.getMyInfo(function (e, info) { + displayNames[info.curvePublic] = info.displayName; + }); + messenger.getFriendList(function (e, keys) { - keys.forEach(function (k) { - messenger.openFriendChannel(k, function (e) { + var count = keys.length + 1; + + var ready = function () { + count--; + if (count === 0) { + initializing = false; + Cryptpad.removeLoadingScreen(); + } + }; + + ready(); + keys.forEach(function (curvePublic) { + messenger.getFriendInfo(curvePublic, function (e, info) { if (e) { return void console.error(e); } - // don't add friends that are already in your userlist - if (friendExistsInUserList(k)) { return; } - - messenger.getFriendInfo(k, function (e, info) { - if (e) { return console.error(e); } - var curvePublic = info.curvePublic; - var name = displayNames[curvePublic] = info.displayName; - var friend = markup.friend(info, name); - $userlist.append(friend); + var name = displayNames[curvePublic] = info.displayName; + initChannel(state, curvePublic, info); + + var chatbox = markup.chatbox(curvePublic, info); + $(chatbox).hide(); + $messages.append(chatbox); + + var friend = markup.friend(info, name); + $userlist.append(friend); + messenger.openFriendChannel(curvePublic, function (e) { + if (e) { return void console.error(e); } + ready(info); + ready(); updateStatus(curvePublic); + // don't add friends that are already in your userlist + //if (friendExistsInUserList(k)) { return; } }); }); });