From d4dd58e7dfb4d321a325282f42cfa629959b3299 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 7 Sep 2018 19:35:06 +0200 Subject: [PATCH] Add a pad room when the messenger is active in a pad --- www/common/common-messenger.js | 112 ++++++++++++++++++------ www/common/outer/async-store.js | 6 +- www/common/sframe-app-framework.js | 2 + www/common/sframe-common-outer.js | 10 ++- www/common/sframe-common.js | 13 +++ www/common/sframe-protocol.js | 1 + www/contacts/messenger-ui.js | 135 +++++++++++++++++++---------- 7 files changed, 203 insertions(+), 76 deletions(-) diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index 73275a2cc..354097393 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -67,7 +67,7 @@ define([ update: [], friend: [], unfriend: [], - ready: [] + event: [] }, range_requests: {}, }; @@ -189,6 +189,8 @@ define([ var friend = getFriendFromChannel(id); if (!friend) { return void cb('NO_SUCH_FRIEND'); } friend.lastKnownHash = hash; + } else if (channel.isPadChat) { + // Nothing to do } else { // TODO room return void cb('NOT_IMPLEMENTED'); @@ -200,6 +202,7 @@ define([ var checkFriendData = function (curve, data) { if (curve === proxy.curvePublic) { return; } var friend = getFriend(proxy, curve); + if (!friend) { return; } var types = []; Object.keys(data).forEach(function (k) { if (friend[k] !== data[k]) { @@ -215,17 +218,21 @@ define([ // Id message allows us to map a netfluxId with a public curve key var onIdMessage = function (msg, sender) { - var channel; - var isId = Object.keys(channels).some(function (chanId) { - if (channels[chanId].userList.indexOf(sender) !== -1) { - channel = channels[chanId]; - return true; - } - }); + var channel, parsed0; - if (!isId) { return; } + try { + parsed0 = JSON.parse(msg); + channel = channels[parsed0.channel]; + if (!channel) { return; } + if (channel.userList.indexOf(sender) === -1) { return; } + } catch (e) { + console.log(msg); + console.error(e); + // Not an ID message + return; + } - var decryptedMsg = channel.encryptor.decrypt(msg); + var decryptedMsg = channel.encryptor.decrypt(parsed0.msg); if (decryptedMsg === null) { return void console.error("Failed to decrypt message"); @@ -261,7 +268,11 @@ define([ var rMsg = [Types.mapIdAck, myData, channel.wc.myID]; var rMsgStr = JSON.stringify(rMsg); var cryptMsg = channel.encryptor.encrypt(rMsgStr); - network.sendto(sender, cryptMsg); + var data = { + channel: channel.id, + msg: cryptMsg + }; + network.sendto(sender, JSON.stringify(data)); }; var orderMessages = function (channel, new_messages /*, sig */) { @@ -295,15 +306,19 @@ define([ author: parsedMsg[1], time: parsedMsg[2], text: parsedMsg[3], - channel: channel.id + channel: channel.id, + name: parsedMsg[4] // Display name for multi-user rooms // this makes debugging a whole lot easier //curve: getCurveForChannel(channel.id), }; channel.messages.push(res); - eachHandler('message', function (f) { - f(res); - }); + if (!joining[channel.id]) { + // Channel is ready + eachHandler('message', function (f) { + f(res); + }); + } return true; } @@ -414,7 +429,8 @@ define([ author: O.d[1], time: O.d[2], text: O.d[3], - channel: req.chanId + channel: req.chanId, + name: O.d[4] }; }); @@ -516,7 +532,7 @@ define([ var getChannelMessagesSince = function (chan, data, keys) { console.log('Fetching [%s] messages since [%s]', chan.id, data.lastKnownHash || ''); var cfg = { - validateKey: keys.validateKey, + validateKey: keys ? keys.validateKey : undefined, owners: [proxy.edPublic, data.edPublic], lastKnownHash: data.lastKnownHash }; @@ -529,11 +545,12 @@ define([ var openChannel = function (data) { var keys = data.keys; - var encryptor = Curve.createEncryptor(keys); + var encryptor = data.encryptor || Curve.createEncryptor(keys); network.join(data.channel).then(function (chan) { var channel = channels[data.channel] = { id: data.channel, isFriendChat: data.isFriendChat, + isPadChat: data.isPadChat, sending: false, encryptor: encryptor, messages: [], @@ -556,7 +573,11 @@ define([ var msg = [Types.mapId, myData, chan.myID]; var msgStr = JSON.stringify(msg); var cryptMsg = channel.encryptor.encrypt(msgStr); - network.sendto(peer, cryptMsg); + var data = { + channel: channel.id, + msg: cryptMsg + }; + network.sendto(peer, JSON.stringify(data)); }; chan.members.forEach(function (peer) { if (peer === Msg.hk) { return; } @@ -758,15 +779,16 @@ define([ // TODO send event chat ready // Remove spinner in chatbox ready = true; - eachHandler('ready', function (f) { - f(); + eachHandler('event', function (f) { + f('READY'); }); }); }; init(); - var getRooms = function (curvePublic, cb) { - if (curvePublic) { + var getRooms = function (data, cb) { + if (data && data.curvePublic) { + var curvePublic = data.curvePublic; // We need to get data about a new friend's room var friend = getFriend(proxy, curvePublic); if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); } @@ -777,7 +799,18 @@ define([ isFriendChat: true, name: friend.displayName, lastKnownHash: friend.lastKnownHash, - curvePublic: friend.curvePublic + curvePublic: friend.curvePublic, + messages: channel.messages + }]); + } + + if (data && data.padChat) { + var pCChannel = getChannel(data.padChat); + if (!pCChannel) { return void cb({error: 'NO_SUCH_CHANNEL'}); } + return void cb([{ + id: pCChannel.id, + isPadChat: true, + messages: pCChannel.messages }]); } @@ -798,7 +831,8 @@ define([ isFriendChat: r.isFriendChat, name: name, lastKnownHash: lastKnownHash, - curvePublic: curvePublic + curvePublic: curvePublic, + messages: r.messages }; }).filter(function (x) { return x; }); cb(rooms); @@ -814,7 +848,30 @@ define([ } else { // TODO room userlist in rooms... // (this is the static userlist, not the netflux one) - } + cb([]); + } + }; + + var openPadChat = function (data, cb) { + var channel = data.channel; + var keys = data.secret && data.secret.keys; + var cryptKey = keys.viewKeyStr ? Crypto.b64AddSlashes(keys.viewKeyStr) : data.secret.key; + var encryptor = Crypto.createEncryptor(cryptKey); + var chanData = { + encryptor: encryptor, + channel: data.channel, + isPadChat: true, + //lastKnownHash: friend.lastKnownHash, + //owners: [proxy.edPublic, friend.edPublic], + //isFriendChat: true + }; + openChannel(chanData); + joining[channel] = function () { + eachHandler('event', function (f) { + f('PADCHAT_READY', channel); + }); + }; + cb(); }; messenger.execCommand = function (obj, cb) { @@ -829,6 +886,9 @@ define([ if (cmd === 'GET_USERLIST') { return void getUserList(data, cb); } + if (cmd === 'OPEN_PAD_CHAT') { + return void openPadChat(data, cb); + } }; Object.freeze(messenger); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 23594db6e..eebda59ff 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1372,10 +1372,10 @@ define([ removedByMe: removedByMe }); }); - messenger.on('ready', function () { - console.log('here'); + messenger.on('event', function (ev, data) { sendMessengerEvent('CHAT_EVENT', { - ev: 'READY' + ev: ev, + data: data }); }); }; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index cb6cb4acd..3e79526d8 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -322,6 +322,8 @@ define([ if (!readOnly) { onLocal(); } evOnReady.fire(newPad); + common.openPadChat(onLocal); + UI.removeLoadingScreen(emitResize); var privateDat = cpNfInner.metadataMgr.getPrivateData(); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0bdabf981..781736958 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -776,11 +776,19 @@ define([ Cryptpad.messenger.setChannelHead(opt, cb); }); + sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) { + Cryptpad.messenger.execCommand({ + cmd: 'OPEN_PAD_CHAT', + data: { + channel: data, + secret: secret + } + }, cb); + }); sframeChan.on('Q_CHAT_COMMAND', function (data, cb) { Cryptpad.messenger.execCommand(data, cb); }); Cryptpad.messenger.onEvent.reg(function (data) { - console.log(data); sframeChan.event('EV_CHAT_EVENT', data); }); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index a68a011fb..e3b9ab59d 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -161,6 +161,19 @@ define([ }); }; + // Chat + funcs.openPadChat = function (saveChanges) { + var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata())); + var channel = md.chat || Hash.createChannelId(); + if (!md.chat) { + md.chat = channel; + ctx.metadataMgr.updateMetadata(md); + setTimeout(saveChanges); + } + ctx.sframeChan.query('Q_CHAT_OPENPADCHAT', channel, function (err, obj) { + if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); } + }); + }; // CodeMirror funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index a61562887..7e566b9bb 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -174,6 +174,7 @@ define({ // Chat 'EV_CHAT_EVENT': true, 'Q_CHAT_COMMAND': true, + 'Q_CHAT_OPENPADCHAT': true, // Put one or more entries to the localStore which will go in localStorage. 'EV_LOCALSTORE_PUT': true, diff --git a/www/contacts/messenger-ui.js b/www/contacts/messenger-ui.js index 00d5a49c4..517008c80 100644 --- a/www/contacts/messenger-ui.js +++ b/www/contacts/messenger-ui.js @@ -24,13 +24,19 @@ define([ var initChannel = function (state, info) { console.log('initializing channel for [%s]', info.id); + var h, t; + if (Array.isArray(info.messages) && info.messages.length) { + h = info.messages[info.messages.length -1].sig; + t = info.messages[0].sig; + } state.channels[info.id] = { - messages: [], + messages: info.messages || [], name: info.name, isFriendChat: info.isFriendChat, + isPadChat: info.isPadChat, curvePublic: info.curvePublic, - HEAD: info.lastKnownHash, - TAIL: null, + HEAD: h || info.lastKnownHash, + TAIL: t || null, }; }; @@ -114,7 +120,7 @@ define([ var markup = {}; markup.message = function (msg) { var curvePublic = msg.author; - var name = contactsData[msg.author].displayName; + var name = msg.name || contactsData[msg.author].displayName; return h('div.cp-app-contacts-message', { title: msg.time? new Date(msg.time).toLocaleString(): '?', 'data-user': curvePublic, @@ -172,9 +178,13 @@ define([ return; } - history.forEach(function (msg) { + history.forEach(function (msg, i) { if (channel.exhausted) { return; } if (msg.sig) { + if (i === 0 && history.length > 1 && msg.sig === channel.TAIL) { + // First message is usually the lastKnownHash, ignore it + return; + } if (msg.sig === channel.TAIL) { console.error('No more messages to fetch'); channel.exhausted = true; @@ -227,7 +237,7 @@ define([ var header = h('div.cp-app-contacts-header', [ avatar, moreHistory, - removeHistory, + data.isFriendChat ? removeHistory: undefined, ]); var messages = h('div.cp-app-contacts-messages'); var input = h('textarea', { @@ -242,18 +252,16 @@ define([ ]); var $avatar = $(avatar); - if (data.isFriendChat) { - var friend = contactsData[curvePublic]; - if (friend.avatar && avatars[friend.avatar]) { - $avatar.append(avatars[friend.avatar]).append(rightCol); - } else { - common.displayAvatar($avatar, friend.avatar, friend.displayName, function ($img) { - if (friend.avatar && $img) { - avatars[friend.avatar] = $img[0].outerHTML; - } - $(rightCol).insertAfter($avatar); - }); - } + var friend = contactsData[curvePublic] || {}; + if (friend.avatar && avatars[friend.avatar]) { + $avatar.append(avatars[friend.avatar]).append(rightCol); + } else { + common.displayAvatar($avatar, friend.avatar, displayName, function ($img) { + if (friend.avatar && $img) { + avatars[friend.avatar] = $img[0].outerHTML; + } + $(rightCol).insertAfter($avatar); + }); } var sending = false; @@ -368,7 +376,13 @@ define([ var $more = $chat.find('.cp-app-contacts-more-history'); $more.click(); } - return void $chat.show(); + $chat.show(); + if (channel.isPadChat) { + // Always scroll bottom for now in pad chat (no last known hash) + var $messagebox = $chat.find('.cp-app-contacts-messages'); + $messagebox.scrollTop($messagebox.outerHeight()); + } + return; } else { console.error("Chat is missing... Please reload the page and try again."); } @@ -389,10 +403,14 @@ define([ var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', { title: Messages.contacts_remove }); + var leaveRoom = h('span.cp-app-contacts-remove.fa.fa-sign-out', { + title: 'Leave this room' // XXX + }); + var status = h('span.cp-app-contacts-status'); var rightCol = h('span.cp-app-contacts-right-col', [ h('span.cp-app-contacts-name', [room.name]), - remove, + room.isFriendChat ? remove : leaveRoom, ]); var friendData = room.isFriendChat ? userlist[0] : {}; @@ -406,23 +424,20 @@ define([ $(remove).click(function (e) { e.stopPropagation(); var channel = state.channels[id]; - if (channel.isFriendChat) { - var curvePublic = channel.curvePublic; - var friend = contactsData[curvePublic] || friendData; - UI.confirm(Messages._getKey('contacts_confirmRemove', [ - Util.fixHTML(friend.name) - ]), function (yes) { - if (!yes) { return; } - removeFriend(curvePublic, function (e) { - if (e) { return void console.error(e); } - }); - // 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) - }, undefined, true); - } else { - // TODO room remove room - } + if (!channel.isFriendChat) { return; } + var curvePublic = channel.curvePublic; + var friend = contactsData[curvePublic] || friendData; + UI.confirm(Messages._getKey('contacts_confirmRemove', [ + Util.fixHTML(friend.name) + ]), function (yes) { + if (!yes) { return; } + removeFriend(curvePublic, function (e) { + if (e) { return void console.error(e); } + }); + // 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) + }, undefined, true); }); if (friendData.avatar && avatars[friendData.avatar]) { @@ -457,6 +472,7 @@ define([ var channel = state.channels[chanId]; if (!channel) { + console.log(message); console.error('expected channel [%s] to be open', chanId); return; } @@ -568,31 +584,40 @@ define([ execCommand('GET_USERLIST', {id: id}, function (e, list) { if (e || list.error) { return void console.error(e || list.error); } - if (!Array.isArray(list) || !list.length) { + if (!room.isPadChat && (!Array.isArray(list) || !list.length)) { return void console.error("Empty room!"); } debug('userlist: ' + JSON.stringify(list)); - // This is a friend, the userlist is only one user. - var friend = list[0]; - contactsData[friend.curvePublic] = friend; + var friend = {}; + + if (room.isFriendChat) { + // This is a friend, the userlist is only one user. + friend = list[0]; + contactsData[friend.curvePublic] = friend; + } var chatbox = markup.chatbox(id, room, friend.curvePublic); $(chatbox).hide(); $messages.append(chatbox); + var $messagebox = $(chatbox).find('.cp-app-contacts-messages'); + room.messages.forEach(function (msg) { + var el_message = markup.message(msg); + $messagebox.append(el_message); + }); + normalizeLabels($messagebox); + var roomEl = markup.room(id, room, list); $userlist.append(roomEl); updateStatus(id); }); - - // TODO room group chat }; messenger.on('friend', function (curvePublic) { debug('new friend: ', curvePublic); - execCommand('GET_ROOMS', curvePublic, function (err, rooms) { + execCommand('GET_ROOMS', {curvePublic: curvePublic}, function (err, rooms) { if (err) { return void console.error(err); } debug('rooms: ' + JSON.stringify(rooms)); rooms.forEach(initializeRoom); @@ -636,16 +661,34 @@ define([ $container.removeClass('cp-app-contacts-initializing'); }; + var onPadChatReady = function (data) { + execCommand('GET_ROOMS', {padChat: data}, function (err, rooms) { + if (err) { return void console.error(err); } + if (!Array.isArray(rooms) || rooms.length !== 1) { + return void console.error('Invalid pad chat'); + } + var room = rooms[0]; + room.name = 'XXX Pad chat'; + rooms.forEach(initializeRoom); + }); + + $container.removeClass('cp-app-contacts-initializing'); + }; + // Initialize chat when outer is ready (all channels loaded) // TODO: try again in outer if fail to load a channel execCommand('IS_READY', null, function (err, yes) { if (yes) { onMessengerReady(); } }); - sframeChan.on('EV_CHAT_EVENT', function (data) { - if (data.ev === 'READY') { + sframeChan.on('EV_CHAT_EVENT', function (obj) { + if (obj.ev === 'READY') { onMessengerReady(); return; } + if (obj.ev === 'PADCHAT_READY') { + onPadChatReady(obj.data); + return; + } }); };