From ce2d0d5b8316c63ee6cc2cbb23b9b9e56b987fc6 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 15 May 2019 18:22:39 +0200 Subject: [PATCH 01/15] Mailbox and notifications prototype --- www/common/outer/async-store.js | 16 +- www/common/outer/mailbox.js | 226 +++++++++++++++++++++++++++- www/common/sframe-common-mailbox.js | 127 ++++++++++++++++ www/common/sframe-common-outer.js | 7 + www/common/sframe-common.js | 4 + 5 files changed, 372 insertions(+), 8 deletions(-) create mode 100644 www/common/sframe-common-mailbox.js diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index dd7c348d9..5b3dcf985 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -625,6 +625,13 @@ define([ // Set the display name (username) in the proxy Store.setDisplayName = function (clientId, value, cb) { + if (store.mailbox) { + // XXX test mailbox, should be removed in prod + store.mailbox.post('notifications', 'NAME_CHANGED', { + old: store.proxy[Constants.displayNameKey], + new: value + }); + } store.proxy[Constants.displayNameKey] = value; broadcast([clientId], "UPDATE_METADATA"); if (store.messenger) { store.messenger.updateMyData(); } @@ -950,6 +957,7 @@ define([ // Mailbox Store.mailbox = { execCommand: function (clientId, data, cb) { + if (!store.loggedIn) { return void cb(); } if (!store.mailbox) { return void cb ({error: 'Mailbox is disabled'}); } store.mailbox.execCommand(clientId, data, cb); } @@ -1092,6 +1100,7 @@ define([ channel.queue.forEach(function (data) { channel.sendMessage(data.message, clientId); }); + channel.queue = []; channel.bcast("PAD_CONNECT", { myID: wc.myID, id: wc.id, @@ -1310,13 +1319,15 @@ define([ if (messengerIdx !== -1) { messengerEventClients.splice(messengerIdx, 1); } - // TODO mailbox events try { store.cursor.removeClient(clientId); } catch (e) { console.error(e); } try { store.onlyoffice.removeClient(clientId); } catch (e) { console.error(e); } + try { + store.mailbox.removeClient(clientId); + } catch (e) { console.error(e); } Object.keys(Store.channels).forEach(function (chanId) { var chanIdx = Store.channels[chanId].clients.indexOf(clientId); @@ -1413,6 +1424,9 @@ define([ }; var loadMailbox = function (waitFor) { + if (!store.loggedIn || !store.proxy.edPublic) { + return; + } store.mailbox = Mailbox.init(store, waitFor, function (ev, data, clients) { clients.forEach(function (cId) { postMessage(cId, 'MAILBOX_EVENT', { diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index ab9d24d58..5f5297744 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -1,30 +1,242 @@ define([ '/common/common-util.js', '/common/common-constants.js', + '/common/common-realtime.js', '/customize/messages.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Constants, Messages, CpNetflux, Crypto) { +], function (Util, Constants, Realtime, Messages, CpNetflux, Crypto) { var Mailbox = {}; + var TYPES = [ + 'notifications' + ]; + var BLOCKING_TYPES = [ + ]; + +/* +proxy.mailboxes = { + friends: { + keys: '', + channel: '', + hash: '', + lastKnownHash: '', + viewed: [] + } +}; + +*/ + + var isMessageNew = function (hash, m) { + return (m.viewed || []).indexOf(hash) === -1 && hash !== m.lastKnownHash; + }; + + var showMessage = function (ctx, type, msg, cId) { + ctx.emit('MESSAGE', { + type: type, + content: msg + }, cId ? [cId] : ctx.clients); + }; + + var openChannel = function (ctx, type, m, onReady) { + var box = ctx.boxes[type] = { + queue: [], + history: [], + sendMessage: function (msg) { // To send a message to our box + try { + msg = JSON.stringify(msg); + } catch (e) { + console.error(e); + } + box.queue.push(msg); + } + }; + /* + // XXX + if (!Crypto.Mailbox) { + return void console.error("chainpad-crypto is outdated and doesn't support mailboxes."); + } + var crypto = Crypto.Mailbox.createEncryptor(); + */ + var crypto = { + encrypt: function (x) { return x; }, + decrypt: function (x) { return x; } + }; + var cfg = { + network: ctx.store.network, + channel: m.channel, // TODO + noChainPad: true, + crypto: crypto, + owners: [ctx.store.proxy.edPublic], + lastKnownHash: m.lastKnownHash + }; + cfg.onConnect = function (wc, sendMessage) { + // Send a message to our box? + box.sendMessage = function (msg) { + try { + msg = JSON.stringify(msg); + } catch (e) { + console.error(e); + } + sendMessage(msg, function (err, hash) { + if (m.viewed.indexOf(hash) === -1) { + m.viewed.push(hash); + } + }); + }; + box.queue.forEach(function (msg) { + box.sendMessage(msg); + }); + box.queue = []; + }; + cfg.onMessage = function (msg, user, vKey, isCpi, hash) { + // TODO + try { + msg = JSON.parse(msg); + } catch (e) { + console.error(e); + } + if (isMessageNew(hash, m)) { + // Message should be displayed + var message = { + msg: msg, + hash: hash + }; + box.history.push(message); + showMessage(ctx, type, message); + } else { + // Message has already been viewer by the user + if (history.length === 0) { + m.lastKnownHash = hash; + } + console.log(hash + ' is not new'); + } + }; + cfg.onReady = function () { + onReady(); + }; + CpNetflux.start(cfg); + }; + + // Send a message to someone else + var sendTo = function () { + + }; + + // Mark a message as read + var dismiss = function (ctx, data, cId, cb) { + var type = data.type; + var hash = data.hash; + var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); + if (!m) { return void cb({error: 'NOT_FOUND'}); } + var box = ctx.boxes[type]; + if (!box) { return void cb({error: 'NOT_LOADED'}); } + + // If the hash in in our history, get the index from the history: + // - if the index is 0, we can change our lastKnownHash + // - otherwise, just push to view + var idx; + if (box.history.some(function (el, i) { + if (hash === el.hash) { + idx = i; + return true; + } + })) { + if (idx === 0) { + m.lastKnownHash = hash; + } else if (m.viewed.indexOf(hash) === -1) { + m.viewed.push(hash); + } + } + + // Check the "viewed" array to see if we're able to bump lastKnownhash more + var sliceIdx; + box.history.some(function (el, i) { + var isViewed = m.viewed.indexOf(el.hash); + if (isViewed !== -1) { + sliceIdx = i + 1; + m.viewed.splice(isViewed, 1); + return false; + } + return true; + }); + if (sliceIdx) { + box.history = box.history.slice(sliceIdx); + } + + Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { + cb(); + }); + }; + + var subscribe = function (ctx, data, cId, cb) { + // Get existing notifications + Object.keys(ctx.boxes).forEach(function (type) { + ctx.boxes[type].history.forEach(function (obj) { + showMessage(ctx, type, obj, cId); + }); + }); + // Subscribe to new notifications + var idx = ctx.clients.indexOf(cId); + if (idx === -1) { + ctx.clients.push(cId); + } + cb(); + }; + + var removeClient = function (ctx, cId) { + var idx = ctx.clients.indexOf(cId); + ctx.clients.splice(idx, 1); + }; + Mailbox.init = function (store, waitFor, emit) { var mailbox = {}; var ctx = { store: store, emit: emit, + clients: [], + boxes: {} }; - mailbox.removeClient = function (clientId) { - // TODO - //removeClient(ctx, clientId); + var mailboxes = store.proxy.mailboxes || {}; + Object.keys(mailboxes).forEach(function (key) { + if (TYPES.indexOf(key) === -1) { return; } + var m = mailboxes[key]; + + if (BLOCKING_TYPES.indexOf(key) === -1) { + openChannel(ctx, key, m, function () { + console.log(key + ' mailbox is ready'); + }); + } else { + openChannel(ctx, key, m, waitFor(function () { + console.log(key + ' mailbox is ready'); + })); + } + }); + + // XXX test function used to populate a mailbox, should be removed in prod + mailbox.post = function (box, type, content) { + var b = ctx.boxes[box]; + if (!b) { return; } + b.sendMessage({ + type: type, + content: content, + sender: store.proxy.curvePublic + }); }; - mailbox.leavePad = function (padChan) { - // TODO - //leaveChannel(ctx, padChan); + + mailbox.removeClient = function (clientId) { + removeClient(ctx, clientId); }; mailbox.execCommand = function (clientId, obj, cb) { var cmd = obj.cmd; var data = obj.data; + if (cmd === 'SUBSCRIBE') { + return void subscribe(ctx, data, clientId, cb); + } + if (cmd === 'DISMISS') { + return void dismiss(ctx, data, clientId, cb); + } }; return mailbox; diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js new file mode 100644 index 000000000..dd1f93172 --- /dev/null +++ b/www/common/sframe-common-mailbox.js @@ -0,0 +1,127 @@ +define([ + 'jquery', + '/common/common-util.js', + '/common/common-interface.js', + '/common/common-ui-elements.js', + '/customize/messages.js' +], function ($, Util, UI, UIElements, Messages) { + var Mailbox = {}; + + Mailbox.create = function (Common) { + var mailbox = {}; + var metadataMgr = Common.getMetadataMgr(); + var sframeChan = Common.getSframeChannel(); + + var execCommand = function (cmd, data, cb) { + sframeChan.query('Q_MAILBOX_COMMAND', { + cmd: cmd, + data: data + }, function (err, obj) { + if (err) { return void cb({error: err}); } + cb(obj); + }); + }; + + var history = {}; + + var removeFromHistory = function (type, hash) { + history[type] = history[type].filter(function (obj) { + return obj.hash !== hash; + }); + }; + + mailbox.dismiss = function (type, hash, cb) { + execCommand('DISMISS', { + hash: hash, + type: type + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + removeFromHistory(type, hash); + cb(); + }); + }; + + mailbox.sendTo = function (user, type, content) { + + }; + + // UI + + var onViewedHandlers = []; + var onMessageHandlers = []; + + // Call the onMessage handlers + var pushMessage = function (data) { + onMessageHandlers.forEach(function (f) { + try { + f(data); + } catch (e) { + console.error(e); + } + }); + }; + + // Get all existing notifications + the new ones when they come + mailbox.subscribe = function (cfg) { + if (typeof(cfg.onViewed) === "function") { + onViewedHandlers.push(cfg.onViewed); + } + if (typeof(cfg.onMessage) === "function") { + onMessageHandlers.push(cfg.onMessage); + } + Object.keys(history).forEach(function (type) { + history[type].forEach(function (data) { + pushMessage({ + type: type, + content: data + }); + }); + }); + }; + + var onViewed = function (data) { + // data = { type: 'type', hash: 'hash' } + onViewedHandlers.forEach(function (f) { + try { + f(data); + } catch (e) { + console.error(e); + } + }); + removeFromHistory(data.type, data.hash); + }; + + var onMessage = function (data) { + // data = { type: 'type', content: {msg: 'msg', hash: 'hash'} } + console.log(data.content); + pushMessage(data); + if (!history[data.type]) { history[data.type] = []; } + history[data.type].push(data.content); + }; + + + // CHANNEL WITH WORKER + + sframeChan.on('EV_MAILBOX_EVENT', function (obj) { + // obj = { ev: 'type', data: obj } + var ev = obj.ev; + var data = obj.data; + if (ev === 'MESSAGE') { + return void onMessage(data); + } + if (ev === 'VIEWED') { + return void onViewed(data); + } + }); + + execCommand('SUBSCRIBE', null, function () { + console.log('subscribed'); + }); + + return mailbox; + }; + + return Mailbox; +}); + + diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index dc1031a83..5c164a3eb 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -877,6 +877,13 @@ define([ Cryptpad.cursor.execCommand(data, cb); }); + Cryptpad.mailbox.onEvent.reg(function (data) { + sframeChan.event('EV_MAILBOX_EVENT', data); + }); + sframeChan.on('Q_MAILBOX_COMMAND', function (data, cb) { + Cryptpad.mailbox.execCommand(data, cb); + }); + Cryptpad.onTimeoutEvent.reg(function () { sframeChan.event('EV_WORKER_TIMEOUT'); }); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 8a59e3d41..3959be66e 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -10,6 +10,7 @@ define([ '/common/sframe-common-file.js', '/common/sframe-common-codemirror.js', '/common/sframe-common-cursor.js', + '/common/sframe-common-mailbox.js', '/common/metadata-manager.js', '/customize/application_config.js', @@ -33,6 +34,7 @@ define([ File, CodeMirror, Cursor, + Mailbox, MetadataMgr, AppConfig, CommonRealtime, @@ -630,6 +632,8 @@ define([ ctx.sframeChan.ready(); cb(funcs); + + Mailbox.create(funcs); }); } }; }); From 68bd563989510b69593c2166d8574c07e234c2b1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 16 May 2019 16:09:42 +0200 Subject: [PATCH 02/15] Mailbox UI temp --- www/common/sframe-app-framework.js | 3 ++- www/common/toolbar3.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index b905c9ffd..00125e1d5 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -605,7 +605,8 @@ define([ 'newpad', 'share', 'limit', - 'unpinnedWarning' + 'unpinnedWarning', + 'notifications' ], title: title.getTitleConfig(), metadataMgr: cpNfInner.metadataMgr, diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 41a4f0731..4c53df130 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -37,6 +37,7 @@ MessengerUI, Messages) { var TITLE_CLS = Bar.constants.title = "cp-toolbar-title"; var NEWPAD_CLS = Bar.constants.newpad = "cp-toolbar-new"; var LINK_CLS = Bar.constants.link = "cp-toolbar-link"; + var NOTIFICATIONS_CLS = Bar.constants.user = 'cp-toolbar-notifications'; // User admin menu var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown'; @@ -70,6 +71,7 @@ MessengerUI, Messages) { 'class': USER_CLS }).appendTo($topContainer); $('', {'class': LIMIT_CLS}).hide().appendTo($userContainer); + $('', {'class': NOTIFICATIONS_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('', {'class': NEWPAD_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); @@ -927,6 +929,12 @@ MessengerUI, Messages) { return $userAdmin; }; + var createNotifications = function (toolbar, config) { + console.log(Common.mailbox); + var $userAdmin = toolbar.$userAdmin.find('.'+NOTIFICATIONS_CLS).show(); + return $userAdmin; + }; + // Events var initClickEvents = function (toolbar) { var removeDropdowns = function () { @@ -1095,6 +1103,7 @@ MessengerUI, Messages) { tb['newpad'] = createNewPad; tb['useradmin'] = createUserAdmin; tb['unpinnedWarning'] = createUnpinnedWarning; + tb['notifications'] = createNotifications; var addElement = toolbar.addElement = function (arr, additionalCfg, init) { if (typeof additionalCfg === "object") { $.extend(true, config, additionalCfg); } From 62e128a9fe6de0e03b4cd0eba707d72dd91d64d4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 16:19:41 +0200 Subject: [PATCH 03/15] Store and display new notifications --- customize.dist/src/less2/include/toolbar.less | 22 +++- www/common/common-ui-elements.js | 2 +- www/common/outer/mailbox.js | 99 ++++++++++++---- www/common/sframe-common-mailbox.js | 108 ++++++++++++------ www/common/sframe-common.js | 2 + www/common/toolbar3.js | 28 ++++- 6 files changed, 195 insertions(+), 66 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 15ef59d62..ec0e3aa60 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -9,6 +9,7 @@ @import (reference) "./icons.less"; @import (reference) "./modal.less"; @import (reference) "./help.less"; +@import (reference) "./notifications.less"; .toolbar_vars ( @color: @colortheme_default-color, // Color of the text for the toolbar @@ -66,6 +67,7 @@ } .help_main(@color, @bg-color); + .notifications_main(); .dropdown_main(); .history_main(); .iconColors_main(); @@ -567,6 +569,17 @@ } .cp-toolbar-user { height: @toolbar_line-height; + .cp-toolbar-notifications { + height: @toolbar_line-height; + width: @toolbar_line-height; + margin-left: 0; + button { + height: @toolbar_line-height; + width: @toolbar_line-height; + font-size: 20px; + margin-top: -1px; + } + } .cp-toolbar-new { height: @toolbar_line-height; width: @toolbar_line-height; @@ -834,7 +847,7 @@ line-height: 28px; // padding + border } } - .cp-toolbar-link, .cp-toolbar-new { + .cp-toolbar-link, .cp-toolbar-new, .cp-toolbar-notifications { font-size: 48px; line-height: 64px; width: @toolbar_top-height; @@ -849,14 +862,13 @@ } transition: all 0.15s; } - .cp-toolbar-new { + .cp-toolbar-notifications, .cp-toolbar-new { background-color: rgba(0,0,0,0.2); &:hover { background-color: rgba(0,0,0,0.3); } text-align: center; font-size: 32px; - margin-left: 10px; &> button { display: flex; align-items: center; @@ -884,6 +896,9 @@ } } } + .cp-toolbar-notifications { + margin-left: 10px; + } .cp-toolbar-link { display: inline-flex; align-items: center; @@ -917,6 +932,7 @@ order: 6; line-height: @toolbar_top-height; color: white; + .cp-toolbar-notifications { order: 1; } .cp-toolbar-new { order: 2; } .cp-toolbar-user-dropdown { order: 3; } .cp-toolbar-backup { order: 4; } // TODO drive migration to secure iframe diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 9418c2e79..4949b76c0 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1425,7 +1425,7 @@ define([ return /HTML/.test(Object.prototype.toString.call(o)) && typeof(o.tagName) === 'string'; }; - var allowedTags = ['a', 'p', 'hr']; + var allowedTags = ['a', 'p', 'hr', 'div']; var isValidOption = function (o) { if (typeof o !== "object") { return false; } if (isElement(o)) { return true; } diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index ba509d8e0..a2f40f74f 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -1,26 +1,33 @@ -// jshint ignore: start define([ '/common/common-util.js', - '/common/common-constants.js', + '/common/common-hash.js', '/common/common-realtime.js', - '/customize/messages.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Constants, Realtime, Messages, CpNetflux, Crypto) { +], function (Util, Hash, Realtime, CpNetflux, Crypto) { var Mailbox = {}; var TYPES = [ - 'notifications' + 'notifications', + 'test' ]; var BLOCKING_TYPES = [ ]; + var initializeMailboxes = function (mailboxes) { + if (!mailboxes['notifications']) { + mailboxes.notifications = { + channel: Hash.createChannelId(), + lastKnownHash: '', + viewed: [] + }; + } + }; + /* proxy.mailboxes = { friends: { - keys: '', channel: '', - hash: '', lastKnownHash: '', viewed: [] } @@ -41,8 +48,9 @@ proxy.mailboxes = { var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { - queue: [], - history: [], + queue: [], // Store the messages to send when the channel is ready + history: [], // All the hashes loaded from the server in corretc order + content: {}, // Content of the messages that should be displayed sendMessage: function (msg) { // To send a message to our box try { msg = JSON.stringify(msg); @@ -52,6 +60,7 @@ proxy.mailboxes = { box.queue.push(msg); } }; + Crypto = Crypto; /* // XXX if (!Crypto.Mailbox) { @@ -90,38 +99,62 @@ proxy.mailboxes = { }); box.queue = []; }; - cfg.onMessage = function (msg, user, vKey, isCpi, hash) { - // TODO + cfg.onMessage = function (msg, user, vKey, isCp, hash) { try { msg = JSON.parse(msg); } catch (e) { console.error(e); } + box.history.push(hash); if (isMessageNew(hash, m)) { // Message should be displayed var message = { msg: msg, hash: hash }; - box.history.push(message); + box.content[hash] = msg; showMessage(ctx, type, message); } else { - // Message has already been viewer by the user - if (history.length === 0) { + // Message has already been viewed by the user + if (Object.keys(box.content).length === 0) { + // If nothing is displayed yet, we can bump our lastKnownHash and remove this hash + // from our "viewed" array m.lastKnownHash = hash; + box.history = []; + var idxViewed = m.viewed.indexOf(hash); + if (idxViewed !== -1) { m.viewed.splice(idxViewed, 1); } } - console.log(hash + ' is not new'); } }; cfg.onReady = function () { + // Clean the "viewed" array: make sure all the "viewed" hashes are + // in history + var toClean = []; + m.viewed.forEach(function (h, i) { + if (box.history.indexOf(h) === -1) { + toClean.push(i); + } + }); + for (var i = toClean.length-1; i>=0; i--) { + m.viewed.splice(toClean[i], 1); + } + // Continue onReady(); }; CpNetflux.start(cfg); }; // Send a message to someone else - var sendTo = function () { + /*var sendTo = function () { + };*/ + + var updateLastKnownHash = function (ctx, type) { + var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); + if (!m) { return; } + var box = ctx.boxes[type]; + if (!box) { return; } + }; // Mark a message as read @@ -138,33 +171,47 @@ proxy.mailboxes = { // - otherwise, just push to view var idx; if (box.history.some(function (el, i) { - if (hash === el.hash) { + if (hash === el) { idx = i; return true; } })) { if (idx === 0) { m.lastKnownHash = hash; + box.history.shift(); + delete box.content[hash]; } else if (m.viewed.indexOf(hash) === -1) { m.viewed.push(hash); } } + // Clear data in memory if needed // Check the "viewed" array to see if we're able to bump lastKnownhash more var sliceIdx; - box.history.some(function (el, i) { - var isViewed = m.viewed.indexOf(el.hash); + var lastKnownHash; + box.history.some(function (hash, i) { + var isViewed = m.viewed.indexOf(hash); if (isViewed !== -1) { sliceIdx = i + 1; m.viewed.splice(isViewed, 1); + lastKnownHash = hash; return false; } return true; }); + if (sliceIdx) { box.history = box.history.slice(sliceIdx); + m.lastKnownHash = lastKnownHash; } + // Make sure we remove data about dismissed messages + Object.keys(box.content).forEach(function (h) { + if (box.history.indexOf(h) === -1 || m.viewed.indexOf(h) !== -1) { + delete box.content[h]; + } + }); + Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { cb(); }); @@ -173,8 +220,12 @@ proxy.mailboxes = { var subscribe = function (ctx, data, cId, cb) { // Get existing notifications Object.keys(ctx.boxes).forEach(function (type) { - ctx.boxes[type].history.forEach(function (obj) { - showMessage(ctx, type, obj, cId); + Object.keys(ctx.boxes[type].content).forEach(function (h) { + var message = { + msg: ctx.boxes[type].content[h], + hash: h + }; + showMessage(ctx, type, message, cId); }); }); // Subscribe to new notifications @@ -199,13 +250,17 @@ proxy.mailboxes = { boxes: {} }; - var mailboxes = store.proxy.mailboxes || {}; + var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; + + initializeMailboxes(mailboxes); + Object.keys(mailboxes).forEach(function (key) { if (TYPES.indexOf(key) === -1) { return; } var m = mailboxes[key]; if (BLOCKING_TYPES.indexOf(key) === -1) { openChannel(ctx, key, m, function () { + updateLastKnownHash(ctx, key); console.log(key + ' mailbox is ready'); }); } else { diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index dd1f93172..c9f0625cf 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -3,13 +3,14 @@ define([ '/common/common-util.js', '/common/common-interface.js', '/common/common-ui-elements.js', + '/common/hyperscript.js', '/customize/messages.js' -], function ($, Util, UI, UIElements, Messages) { +], function ($, Util, UI, UIElements, h, Messages) { var Mailbox = {}; + Messages = Messages; // XXX Mailbox.create = function (Common) { - var mailbox = {}; - var metadataMgr = Common.getMetadataMgr(); + var mailbox = Common.mailbox; var sframeChan = Common.getSframeChannel(); var execCommand = function (cmd, data, cb) { @@ -30,53 +31,57 @@ define([ }); }; - mailbox.dismiss = function (type, hash, cb) { - execCommand('DISMISS', { - hash: hash, - type: type - }, function (obj) { - if (obj && obj.error) { return void cb(obj.error); } - removeFromHistory(type, hash); - cb(); - }); - }; - mailbox.sendTo = function (user, type, content) { - + console.log(user, type, content); }; // UI + var formatData = function (data) { + return JSON.stringify(data.content.msg.content); + }; + var createElement = function (data) { + var notif; + var dismiss = h('span.fa.fa-times'); + dismiss.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + mailbox.dismiss(data, function (err) { + if (err) { return void console.error(err); } + if (notif && notif.parentNode) { + try { + notif.parentNode.removeChild(notif); + } catch (e) { console.error(e); } + } + }); + }); + notif = h('div.cp-notification', { + 'data-hash': data.content.hash + }, [ + h('div.cp-notification-content', h('p', formatData(data))), + h('div.cp-notification-dismiss', dismiss) + ]); + return notif; + }; + + var onViewedHandlers = []; var onMessageHandlers = []; // Call the onMessage handlers - var pushMessage = function (data) { - onMessageHandlers.forEach(function (f) { + var pushMessage = function (data, handler) { + var todo = function (f) { try { - f(data); + var el = createElement(data); + f(data, el); } catch (e) { console.error(e); } - }); - }; - - // Get all existing notifications + the new ones when they come - mailbox.subscribe = function (cfg) { - if (typeof(cfg.onViewed) === "function") { - onViewedHandlers.push(cfg.onViewed); + }; + if (typeof (handler) === "function") { + return void todo(handler); } - if (typeof(cfg.onMessage) === "function") { - onMessageHandlers.push(cfg.onMessage); - } - Object.keys(history).forEach(function (type) { - history[type].forEach(function (data) { - pushMessage({ - type: type, - content: data - }); - }); - }); + onMessageHandlers.forEach(todo); }; var onViewed = function (data) { @@ -99,6 +104,37 @@ define([ history[data.type].push(data.content); }; + mailbox.dismiss = function (data, cb) { + var dataObj = { + hash: data.content.hash, + type: data.type + }; + execCommand('DISMISS', dataObj, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + onViewed(dataObj); + cb(); + }); + }; + + + // Get all existing notifications + the new ones when they come + mailbox.subscribe = function (cfg) { + if (typeof(cfg.onViewed) === "function") { + onViewedHandlers.push(cfg.onViewed); + } + if (typeof(cfg.onMessage) === "function") { + onMessageHandlers.push(cfg.onMessage); + } + Object.keys(history).forEach(function (type) { + history[type].forEach(function (data) { + pushMessage({ + type: type, + content: data + }, cfg.onMessage); + }); + }); + }; + // CHANNEL WITH WORKER diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 3959be66e..76dcaa029 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -470,6 +470,8 @@ define([ }); }; + funcs.mailbox = {}; + Object.freeze(funcs); return { create: function (cb) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 4c53df130..8eb3b6c2d 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -7,9 +7,10 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/common-feedback.js', + '/common/hyperscript.js', '/common/messenger-ui.js', '/customize/messages.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, +], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h, MessengerUI, Messages) { var Common; @@ -929,10 +930,29 @@ MessengerUI, Messages) { return $userAdmin; }; - var createNotifications = function (toolbar, config) { + var createNotifications = function (toolbar) { console.log(Common.mailbox); - var $userAdmin = toolbar.$userAdmin.find('.'+NOTIFICATIONS_CLS).show(); - return $userAdmin; + var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show(); + var div = h('div.cp-notifications-container'); + var pads_options = [div]; + var dropdownConfig = { + text: '', // Button initial text + options: pads_options, // Entries displayed in the menu + container: $notif, + left: true, + common: Common + }; + var $newPadBlock = UIElements.createDropdown(dropdownConfig); + $newPadBlock.find('button').attr('title', Messages.mailbox_title); // XXX + $newPadBlock.find('button').addClass('fa fa-bell-o'); + + Common.mailbox.subscribe({ + onMessage: function (data, el) { + if (el) { div.appendChild(el); } + } + }); + + return $newPadBlock; }; // Events From b15c67e8d4738a573b5718e0fea2bf9d7a020ee4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 16:55:46 +0200 Subject: [PATCH 04/15] Enable encryption in mailboxes and post to other users --- www/common/outer/mailbox.js | 74 ++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index a2f40f74f..0905da650 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -46,6 +46,21 @@ proxy.mailboxes = { }, cId ? [cId] : ctx.clients); }; + var getMyKeys = function (ctx) { + var proxy = ctx.store && ctx.store.proxy; + if (!proxy.curvePrivate || !proxy.curvePublic) { return; } + return { + curvePrivate: proxy.curvePrivate, + curvePublic: proxy.curvePublic + }; + }; + + var getContact = function (ctx, user) { + var proxy = ctx.store && ctx.store.proxy; + if (!proxy.friends || !proxy.friends[user]) { return; } + return proxy.friends[user]; + }; + var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { queue: [], // Store the messages to send when the channel is ready @@ -61,20 +76,22 @@ proxy.mailboxes = { } }; Crypto = Crypto; - /* - // XXX if (!Crypto.Mailbox) { return void console.error("chainpad-crypto is outdated and doesn't support mailboxes."); } - var crypto = Crypto.Mailbox.createEncryptor(); - */ - var crypto = { - encrypt: function (x) { return x; }, - decrypt: function (x) { return x; } - }; + var keys = getMyKeys(ctx); + if (!keys) { return void console.error("missing asymmetric encryption keys"); } + var crypto = Crypto.Mailbox.createEncryptor(keys); + // XXX remove 'test' + if (type === 'test') { + crypto = { + encrypt: function (x) { return x; }, + decrypt: function (x) { return x; } + }; + } var cfg = { network: ctx.store.network, - channel: m.channel, // TODO + channel: m.channel, noChainPad: true, crypto: crypto, owners: [ctx.store.proxy.edPublic], @@ -82,6 +99,7 @@ proxy.mailboxes = { }; cfg.onConnect = function (wc, sendMessage) { // Send a message to our box? + // NOTE: we use our own curvePublic so that we can decrypt our own message :) box.sendMessage = function (msg) { try { msg = JSON.stringify(msg); @@ -92,19 +110,21 @@ proxy.mailboxes = { if (m.viewed.indexOf(hash) === -1) { m.viewed.push(hash); } - }); + }, keys.curvePublic); }; box.queue.forEach(function (msg) { box.sendMessage(msg); }); box.queue = []; }; - cfg.onMessage = function (msg, user, vKey, isCp, hash) { + cfg.onMessage = function (msg, user, vKey, isCp, hash, author) { try { msg = JSON.parse(msg); + console.log(msg); } catch (e) { console.error(e); } + if (author) { msg.author = author; } box.history.push(hash); if (isMessageNew(hash, m)) { // Message should be displayed @@ -145,9 +165,31 @@ proxy.mailboxes = { }; // Send a message to someone else - /*var sendTo = function () { - - };*/ + var sendTo = function (ctx, type, msg, user, cb) { + if (!Crypto.Mailbox) { + return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."}); + } + var keys = getMyKeys(ctx); + if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } + var friend = getContact(ctx, user); + if (!friend || !friend.notifications) { return void cb({error: "no notification channel"}); } + + var crypto = Crypto.Mailbox.createEncryptor(keys); + var network = ctx.store.network; + + var ciphertext = crypto.encrypt(JSON.stringify({ + type: type, + content: msg + }), friend.curvePublic); + + network.join(friend.notification).then(function (wc) { + wc.bcast(ciphertext).then(function () { + cb(); + }); + }, function (err) { + cb({error: err}); + }); + }; var updateLastKnownHash = function (ctx, type) { var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); @@ -281,6 +323,10 @@ proxy.mailboxes = { }); }; + mailbox.sendTo = function (type, msg, user, cb) { + sendTo(ctx, type, msg, user, cb); + }; + mailbox.removeClient = function (clientId) { removeClient(ctx, clientId); }; From 9ead40a3322fb5813c25e03bb5269cb735ca430c Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 17:23:21 +0200 Subject: [PATCH 05/15] Test sending a message to a friend --- www/common/outer/async-store.js | 18 ++++++++++++++++-- www/common/outer/mailbox.js | 24 ++++++++++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 087d746b4..035fabda4 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -625,11 +625,25 @@ define([ // Set the display name (username) in the proxy Store.setDisplayName = function (clientId, value, cb) { - if (store.mailbox) { + if (store.mailbox && store.proxy.friends) { // XXX test mailbox, should be removed in prod - store.mailbox.post('notifications', 'NAME_CHANGED', { + /*store.mailbox.post('notifications', 'NAME_CHANGED', { old: store.proxy[Constants.displayNameKey], new: value + });*/ + Object.keys(store.proxy.friends).forEach(function (curve) { + var f = store.proxy.friends[curve]; + if (!f.notifications) { return; } + store.mailbox.sendTo('NAME_CHANGED', { + old: store.proxy[Constants.displayNameKey], + new: value + }, { + channel: f.notifications, + curvePublic: curve + }, function (obj) { + if (obj && obj.error) { return void console.error(obj.error); } + console.log('notif sent to '+f); + }); }); } store.proxy[Constants.displayNameKey] = value; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 0905da650..4ed0868ca 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -55,12 +55,6 @@ proxy.mailboxes = { }; }; - var getContact = function (ctx, user) { - var proxy = ctx.store && ctx.store.proxy; - if (!proxy.friends || !proxy.friends[user]) { return; } - return proxy.friends[user]; - }; - var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { queue: [], // Store the messages to send when the channel is ready @@ -158,6 +152,11 @@ proxy.mailboxes = { for (var i = toClean.length-1; i>=0; i--) { m.viewed.splice(toClean[i], 1); } + // Listen for changes in the "viewed" and lastKnownHash values + ctx.store.proxy.on('change', ['mailboxes', type], function () { + // Check everything! + // XXX + }); // Continue onReady(); }; @@ -171,8 +170,7 @@ proxy.mailboxes = { } var keys = getMyKeys(ctx); if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } - var friend = getContact(ctx, user); - if (!friend || !friend.notifications) { return void cb({error: "no notification channel"}); } + if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); } var crypto = Crypto.Mailbox.createEncryptor(keys); var network = ctx.store.network; @@ -180,9 +178,9 @@ proxy.mailboxes = { var ciphertext = crypto.encrypt(JSON.stringify({ type: type, content: msg - }), friend.curvePublic); + }), user.curvePublic); - network.join(friend.notification).then(function (wc) { + network.join(user.channel).then(function (wc) { wc.bcast(ciphertext).then(function () { cb(); }); @@ -256,6 +254,12 @@ proxy.mailboxes = { Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { cb(); + ctx.emit('VIEWED', { + type: type, + hash: hash + }, ctx.clients.filter(function (clientId) { + return clientId !== cId; + })); }); }; From 7fd3bba2a64c07fbaadc239d5ab18a7965b6ae5f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 17:42:33 +0200 Subject: [PATCH 06/15] Test sending a message from a pad (inner iframe) --- www/common/outer/async-store.js | 1 + www/common/outer/mailbox.js | 3 +++ www/common/sframe-common-mailbox.js | 10 +++++++++- www/common/sframe-common-title.js | 18 +++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 035fabda4..5b25b0379 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -451,6 +451,7 @@ define([ avatar: Util.find(store.proxy, ['profile', 'avatar']), profile: Util.find(store.proxy, ['profile', 'view']), color: getUserColor(), + notifications: Util.find(store.proxy, ['mailboxes', 'notifications', 'channel']), curvePublic: store.proxy.curvePublic, }, // "priv" is not shared with other users but is needed by the apps diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 4ed0868ca..2f5d84c00 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -343,6 +343,9 @@ proxy.mailboxes = { if (cmd === 'DISMISS') { return void dismiss(ctx, data, clientId, cb); } + if (cmd === 'SENDTO') { + return void sendTo(ctx, data.type, data.msg, data.user, cb); + } }; return mailbox; diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index c9f0625cf..6179f55d8 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -31,8 +31,16 @@ define([ }); }; - mailbox.sendTo = function (user, type, content) { + mailbox.sendTo = function (type, content, user) { console.log(user, type, content); + execCommand('SENDTO', { + type: type, + msg: content, + user: user + }, function (err, obj) { + if (err || (obj && obj.error)) { return void console.error(err || obj.error); } + console.log('notif sent to other user from inner'); + }); }; // UI diff --git a/www/common/sframe-common-title.js b/www/common/sframe-common-title.js index 3e98b853e..d204f5268 100644 --- a/www/common/sframe-common-title.js +++ b/www/common/sframe-common-title.js @@ -69,7 +69,23 @@ define([ return void UI.alert(Messages.pinLimitNotPinned, null, true); } else if (err) { return; } evTitleChange.fire(title); - if (titleUpdated) { titleUpdated(undefined, title); } + if (titleUpdated) { + titleUpdated(undefined, title); + // XXX Test notifications from inner + var users = metadataMgr.getMetadata().users; + var me = metadataMgr.getNetfluxId(); + Object.keys(users).forEach(function (netfluxId) { + if (netfluxId === me) { return; } + var user = users[netfluxId]; + if (!user.curvePublic || !user.notifications) { return; } + Common.mailbox.sendTo("TEST_NOTIF_TITLE", { + new_title: title + }, { + channel: user.notifications, + curvePublic: user.curvePublic + }); + }); + } }); }); From 11a365557c687e1579921cc11363e044ca288b4f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 18:28:15 +0200 Subject: [PATCH 07/15] Clear notifications viewed from another store (browser/worker) --- .../src/less2/include/notifications.less | 34 +++++++++++++++++++ www/common/outer/mailbox.js | 27 ++++++++++++--- www/common/sframe-common-mailbox.js | 14 +++++--- 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 customize.dist/src/less2/include/notifications.less diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less new file mode 100644 index 000000000..d65da5e49 --- /dev/null +++ b/customize.dist/src/less2/include/notifications.less @@ -0,0 +1,34 @@ +@import (reference) "./colortheme-all.less"; + +.notifications_main() { + --LessLoader_require: LessLoader_currentFile(); +} +& { + @notif-height: 50px; + .cp-notifications-container { + max-width: 300px; + display: flex; + flex-flow: column; + .cp-notification { + height: @notif-height; + display: flex; + .cp-notification-content { + flex: 1; + min-width: 0; + } + .cp-notification-dismiss { + color: black; + width: 25px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + span { + cursor: pointer; + } + } + } + } +} + + diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 2f5d84c00..a66fe55b7 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -112,9 +112,9 @@ proxy.mailboxes = { box.queue = []; }; cfg.onMessage = function (msg, user, vKey, isCp, hash, author) { + if (hash === m.lastKnownHash) { return; } try { msg = JSON.parse(msg); - console.log(msg); } catch (e) { console.error(e); } @@ -153,9 +153,28 @@ proxy.mailboxes = { m.viewed.splice(toClean[i], 1); } // Listen for changes in the "viewed" and lastKnownHash values - ctx.store.proxy.on('change', ['mailboxes', type], function () { - // Check everything! - // XXX + var view = function (h) { + delete box.content[h]; + ctx.emit('VIEWED', { + type: type, + hash: h + }, ctx.clients); + }; + ctx.store.proxy.on('change', ['mailboxes', type], function (o, n, p, r) { + if (p[2] === 'lastKnownHash') { + // Hide everything up to this hash + var sliceIdx; + box.history.some(function (h, i) { + sliceIdx = i + 1; + view(h); + if (h === n) { return true; } + }); + box.history = box.history.slice(sliceIdx); + } + if (p[2] === 'viewed') { + // Hide this message + view(n); + } }); // Continue onReady(); diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 6179f55d8..4aca98e56 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -32,14 +32,12 @@ define([ }; mailbox.sendTo = function (type, content, user) { - console.log(user, type, content); execCommand('SENDTO', { type: type, msg: content, user: user }, function (err, obj) { if (err || (obj && obj.error)) { return void console.error(err || obj.error); } - console.log('notif sent to other user from inner'); }); }; @@ -56,11 +54,11 @@ define([ e.stopPropagation(); mailbox.dismiss(data, function (err) { if (err) { return void console.error(err); } - if (notif && notif.parentNode) { + /*if (notif && notif.parentNode) { try { notif.parentNode.removeChild(notif); } catch (e) { console.error(e); } - } + }*/ }); }); notif = h('div.cp-notification', { @@ -76,6 +74,14 @@ define([ var onViewedHandlers = []; var onMessageHandlers = []; + onViewedHandlers.push(function (data) { + var hash = data.hash.replace(/"/g, '\\\"'); + var $notif = $('.cp-notification[data-hash="'+hash+'"]'); + if ($notif.length) { + $notif.remove(); + } + }); + // Call the onMessage handlers var pushMessage = function (data, handler) { var todo = function (f) { From 0732773bba97a25fa48bc28ca51791619693c3c5 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 18:28:45 +0200 Subject: [PATCH 08/15] lint compliance --- www/common/outer/mailbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index a66fe55b7..da7b8bf90 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -160,7 +160,7 @@ proxy.mailboxes = { hash: h }, ctx.clients); }; - ctx.store.proxy.on('change', ['mailboxes', type], function (o, n, p, r) { + ctx.store.proxy.on('change', ['mailboxes', type], function (o, n, p) { if (p[2] === 'lastKnownHash') { // Hide everything up to this hash var sliceIdx; From 991c56fec3454557647e22ab9366d1a1f7075579 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 21 May 2019 18:43:11 +0200 Subject: [PATCH 09/15] Friend requests with mailboxes --- .../src/less2/include/notifications.less | 11 +- www/common/common-messaging.js | 165 ++---------- www/common/common-ui-elements.js | 20 ++ www/common/cryptpad-common.js | 26 +- www/common/outer/async-store.js | 97 +++++--- www/common/outer/mailbox.js | 235 ++++++++++-------- www/common/outer/store-rpc.js | 4 +- www/common/sframe-common-outer.js | 16 +- www/common/sframe-common.js | 31 +-- www/common/toolbar3.js | 33 ++- 10 files changed, 298 insertions(+), 340 deletions(-) diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index d65da5e49..b0d6b22ed 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -15,12 +15,21 @@ .cp-notification-content { flex: 1; min-width: 0; + p { + word-break: break-all; + } + &.cp-clickable { + cursor: pointer; + &:hover { + background-color: rgba(0,0,0,0.1); + } + } } .cp-notification-dismiss { color: black; width: 25px; height: 100%; - display: flex; + display: none; align-items: center; justify-content: center; span { diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index b0d13ec81..a53f0c3b2 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -7,14 +7,7 @@ define([ '/common/common-realtime.js', ], function (Crypto, Hash, Util, Constants, Messages, Realtime) { - var Msg = { - inputs: [], - }; - - // TODO - // - mute a channel (hide notifications or don't open it?) - var pending = {}; - var pendingRequests = []; + var Msg = {}; var createData = Msg.createData = function (proxy, hash) { return { @@ -23,11 +16,13 @@ define([ profile: proxy.profile && proxy.profile.view, edPublic: proxy.edPublic, curvePublic: proxy.curvePublic, + notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']), avatar: proxy.profile && proxy.profile.avatar }; }; - var getFriend = function (proxy, pubkey) { + var getFriend = Msg.getFriend = function (proxy, pubkey) { + if (!pubkey) { return; } if (pubkey === proxy.curvePublic) { var data = createData(proxy); delete data.channel; @@ -56,21 +51,18 @@ define([ return list; }; - // TODO make this internal to the messenger - var channels = Msg.channels = {}; - - Msg.getLatestMessages = function () { - Object.keys(channels).forEach(function (id) { - if (id === 'me') { return; } - var friend = channels[id]; - friend.getMessagesSinceDisconnect(); - friend.refresh(); + Msg.acceptFriendRequest = function (store, data, cb) { + var friend = getFriend(store.proxy, data.curvePublic) || {}; + var myData = createData(store.proxy, friend.channel || data.channel); + store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', myData, { + channel: data.notifications, + curvePublic: data.curvePublic + }, function (obj) { + cb(obj); + if (obj && obj.error) { return void cb(obj); } }); }; - - // Invitation - // FIXME there are too many functions with this name - var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) { + Msg.addToFriendList = function (cfg, data, cb) { var proxy = cfg.proxy; var friends = getFriendList(proxy); var pubKey = data.curvePublic; // todo validata data @@ -85,135 +77,6 @@ define([ if (res.error) { console.error(res.error); } }); }); - cfg.updateMetadata(); - }; - - /* Used to accept friend requests within apps other than /contacts/ */ - Msg.addDirectMessageHandler = function (cfg, href) { - var network = cfg.network; - var proxy = cfg.proxy; - 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 = Hash.parsePadUrl(href); - var secret = Hash.getSecrets(parsed.type, parsed.hash); - if (!parsed.hashData) { return; } - var chan = secret.channel; - // Decrypt - var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key); - var decryptMsg; - try { - decryptMsg = Crypto.decrypt(message, key); - } catch (e) { - // If we can't decrypt, it means it is not a friend request message - } - if (!decryptMsg) { return; } - // Parse - msg = JSON.parse(decryptMsg); - if (msg[1] !== chan) { return; } - var msgData = msg[2]; - var msgStr; - if (msg[0] === "FRIEND_REQ") { - msg = ["FRIEND_REQ_NOK", chan]; - var todo = function (yes) { - if (yes) { - pending[sender] = msgData; - msg = ["FRIEND_REQ_OK", chan, createData(proxy, msgData.channel)]; - } - msgStr = Crypto.encrypt(JSON.stringify(msg), key); - network.sendto(sender, msgStr); - }; - var existing = getFriend(proxy, msgData.curvePublic); - if (existing) { - todo(true); - return; - } - var confirmMsg = Messages._getKey('contacts_request', [ - Util.fixHTML(msgData.displayName) - ]); - cfg.friendRequest(confirmMsg, todo); - return; - } - if (msg[0] === "FRIEND_REQ_OK") { - var idx = pendingRequests.indexOf(sender); - if (idx !== -1) { pendingRequests.splice(idx, 1); } - - // FIXME clarify this function's name - addToFriendList(cfg, msgData, function (err) { - if (err) { - return void cfg.friendComplete({ - logText: Messages.contacts_addError, - netfluxId: sender - }); - } - cfg.friendComplete({ - logText: Messages.contacts_added, - netfluxId: sender, - friend: msgData - }); - 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") { - var i = pendingRequests.indexOf(sender); - if (i !== -1) { pendingRequests.splice(i, 1); } - cfg.friendComplete({ - logText: Messages.contacts_rejected, - netfluxId: sender, - }); - cfg.updateMetadata(); - return; - } - if (msg[0] === "FRIEND_REQ_ACK") { - var data = pending[sender]; - if (!data) { return; } - addToFriendList(cfg, data, function (err) { - if (err) { - return void cfg.friendComplete({ - logText: Messages.contacts_addError, - netfluxId: sender - }); - } - cfg.friendComplete({ - logText: Messages.contacts_added, - netfluxId: sender, - friend: data - }); - }); - return; - } - // TODO: timeout ACK: warn the user - } catch (e) { - console.error("Cannot parse direct message", msg || message, "from", sender, e); - } - }); - }; - - Msg.inviteFromUserlist = function (cfg, data, cb) { - var network = cfg.network; - var netfluxId = data.netfluxId; - var parsed = Hash.parsePadUrl(data.href); - var secret = Hash.getSecrets(parsed.type, parsed.hash); - if (!parsed.hashData) { return; } - // Message - var chan = secret.channel; - var myData = createData(cfg.proxy); - var msg = ["FRIEND_REQ", chan, myData]; - // Encryption - var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key); - var msgStr = Crypto.encrypt(JSON.stringify(msg), key); - // Send encrypted message - if (pendingRequests.indexOf(netfluxId) === -1) { - pendingRequests.push(netfluxId); - cfg.updateMetadata(); // redraws the userlist in pad - } - network.sendto(netfluxId, msgStr); - cb(); }; return Msg; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4949b76c0..3ba27f86f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2607,5 +2607,25 @@ define([ return m; }; + UIElements.displayFriendRequestModal = function (common, data) { + var msg = data.content.msg; + var text = Messages._getKey('contacts_request', [msg.content.displayName]); + UI.confirm(text, function (yes) { + common.getSframeChannel().query("Q_ANSWER_FRIEND_REQUEST", { + data: data, + value: yes + }, function (err, obj) { + var error = err || (obj && obj.error); + if (error) { + return void UI.warn(error); + } + UI.log(Messages.contacts_added); + }); + }, { + ok: 'Accept', // XXX + cancel: 'Ignore the request' // XXX + }, true); + }; + return UIElements; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 027b61929..dde1e9d5e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -604,16 +604,6 @@ define([ }); }; - // Messaging (manage friends from the userlist) - common.inviteFromUserlist = function (netfluxId, cb) { - postMessage("INVITE_FROM_USERLIST", { - netfluxId: netfluxId, - href: window.location.href - }, function (obj) { - if (obj && obj.error) { return void cb(obj.error); } - cb(); - }); - }; // Admin common.adminRpc = function (data, cb) { @@ -625,14 +615,13 @@ define([ common.onNetworkReconnect = Util.mkEvent(); common.onNewVersionReconnect = Util.mkEvent(); - // Messaging + // Messaging (friend requests) var messaging = common.messaging = {}; - messaging.onFriendRequest = Util.mkEvent(); - messaging.onFriendComplete = Util.mkEvent(); - messaging.addHandlers = function (href) { - postMessage("ADD_DIRECT_MESSAGE_HANDLERS", { - href: href - }); + messaging.answerFriendRequest = function (data, cb) { + postMessage("ANSWER_FRIEND_REQUEST", data, cb); + }; + messaging.sendFriendRequest = function (data, cb) { + postMessage("SEND_FRIEND_REQUEST", data, cb); }; // Onlyoffice @@ -1081,9 +1070,6 @@ define([ var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); if (localToken !== data.token) { requestLogin(); } }, - // Messaging - Q_FRIEND_REQUEST: common.messaging.onFriendRequest.fire, - EV_FRIEND_COMPLETE: common.messaging.onFriendComplete.fire, // Network NETWORK_DISCONNECT: common.onNetworkDisconnect.fire, NETWORK_RECONNECT: function (data) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 5b25b0379..ac02e3ce3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -461,7 +461,8 @@ define([ friends: store.proxy.friends || {}, settings: store.proxy.settings, thumbnails: disableThumbnails === false, - isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])) + isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])), + pendingFriends: store.proxy.friends_pending || {} } }; cb(JSON.parse(JSON.stringify(metadata))); @@ -902,36 +903,63 @@ define([ // Messaging (manage friends from the userlist) - var getMessagingCfg = function (clientId) { - return { - proxy: store.proxy, - realtime: store.realtime, - network: store.network, - updateMetadata: function () { - postMessage(clientId, "UPDATE_METADATA"); - }, - pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, - friendComplete: function (data) { - if (data.friend && store.messenger && store.messenger.onFriendAdded) { - store.messenger.onFriendAdded(data.friend); - } - postMessage(clientId, "EV_FRIEND_COMPLETE", data); - }, - friendRequest: function (data, cb) { - postMessage(clientId, "Q_FRIEND_REQUEST", data, cb); - }, + Store.answerFriendRequest = function (clientId, obj, cb) { + console.log(obj); + var value = obj.value; + var data = obj.data; + if (data.type !== 'notifications') { return void cb ({error: 'EINVAL'}); } + var hash = data.content.hash; + var msg = data.content.msg; + + var dismiss = function (cb) { + cb = cb || function () {}; + store.mailbox.dismiss({ + hash: hash, + type: 'notifications' + }, cb); }; + + if (value) { + Messaging.acceptFriendRequest(store, msg.content, function (obj) { + if (obj && obj.error) { return void cb(obj); } + Messaging.addToFriendList({ + proxy: store.proxy, + realtime: store.realtime, + pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, + }, msg.content, function (err) { + broadcast([], "UPDATE_METADATA"); + if (err) { return void cb({error: err}); } + dismiss(cb); + }); + }); + return; + } + dismiss(); }; - Store.inviteFromUserlist = function (clientId, data, cb) { - var messagingCfg = getMessagingCfg(clientId); - Messaging.inviteFromUserlist(messagingCfg, data, cb); - }; - Store.addDirectMessageHandlers = function (clientId, data) { - var messagingCfg = getMessagingCfg(clientId); - Messaging.addDirectMessageHandler(messagingCfg, data.href); - }; + Store.sendFriendRequest = function (clientId, data, cb) { + var friend = Messaging.getFriend(store.proxy, data.curvePublic); + if (friend) { return void cb({error: 'ALREADY_FRIEND'}); } + if (!data.notifications || !data.curvePublic) { return void cb({error: 'INVALID_USER'}); } - // Messenger + store.proxy.friends_pending = store.proxy.friends_pending || {}; + + var twoDaysAgo = +new Date(); // (+new Date() - (2 * 24 * 3600 * 1000)); // XXX + if (store.proxy.friends_pending[data.curvePublic] && + store.proxy.friends_pending[data.curvePublic] > twoDaysAgo) { + return void cb({error: 'TIMEOUT'}); + } + + store.proxy.friends_pending[data.curvePublic] = +new Date(); + broadcast([], "UPDATE_METADATA"); + + var myData = Messaging.createData(store.proxy); + store.mailbox.sendTo('FRIEND_REQUEST', myData, { + channel: data.notifications, + curvePublic: data.curvePublic + }, function (obj) { + cb(obj); + }); + }; // Get hashes for the share button Store.getStrongerHash = function (clientId, data, cb) { @@ -946,6 +974,7 @@ define([ cb(); }; + // Messenger Store.messenger = { execCommand: function (clientId, data, cb) { if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } @@ -1444,7 +1473,13 @@ define([ if (!store.loggedIn || !store.proxy.edPublic) { return; } - store.mailbox = Mailbox.init(store, waitFor, function (ev, data, clients) { + store.mailbox = Mailbox.init({ + store: store, + updateMetadata: function () { + broadcast([], "UPDATE_METADATA"); + }, + pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, + }, waitFor, function (ev, data, clients) { clients.forEach(function (cId) { postMessage(cId, 'MAILBOX_EVENT', { ev: ev, @@ -1606,6 +1641,10 @@ define([ // Trigger userlist update when the friendlist has changed broadcast([], "UPDATE_METADATA"); }); + proxy.on('change', ['friends_pending'], function () { + // Trigger userlist update when the friendlist has changed + broadcast([], "UPDATE_METADATA"); + }); proxy.on('change', ['settings'], function () { broadcast([], "UPDATE_METADATA"); }); diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index da7b8bf90..0b4ce7367 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -2,9 +2,10 @@ define([ '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', + '/common/outer/mailbox-handlers.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Hash, Realtime, CpNetflux, Crypto) { +], function (Util, Hash, Realtime, Handlers, CpNetflux, Crypto) { var Mailbox = {}; var TYPES = [ @@ -14,13 +15,16 @@ define([ var BLOCKING_TYPES = [ ]; - var initializeMailboxes = function (mailboxes) { + var initializeMailboxes = function (ctx, mailboxes) { if (!mailboxes['notifications']) { mailboxes.notifications = { channel: Hash.createChannelId(), lastKnownHash: '', viewed: [] }; + ctx.pinPads([mailboxes.notifications.channel], function (res) { + if (res.error) { console.error(res); } + }); } }; @@ -55,6 +59,108 @@ proxy.mailboxes = { }; }; + // Send a message to someone else + var sendTo = function (ctx, type, msg, user, cb) { + if (!Crypto.Mailbox) { + return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."}); + } + var keys = getMyKeys(ctx); + if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } + if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); } + + var crypto = Crypto.Mailbox.createEncryptor(keys); + var network = ctx.store.network; + + var ciphertext = crypto.encrypt(JSON.stringify({ + type: type, + content: msg + }), user.curvePublic); + + network.join(user.channel).then(function (wc) { + wc.bcast(ciphertext).then(function () { + cb(); + wc.leave(); + }); + }, function (err) { + cb({error: err}); + }); + }; + + var updateLastKnownHash = function (ctx, type) { + var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); + if (!m) { return; } + var box = ctx.boxes[type]; + if (!box) { return; } + + }; + + // Mark a message as read + var dismiss = function (ctx, data, cId, cb) { + var type = data.type; + var hash = data.hash; + var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); + if (!m) { return void cb({error: 'NOT_FOUND'}); } + var box = ctx.boxes[type]; + if (!box) { return void cb({error: 'NOT_LOADED'}); } + + // If the hash in in our history, get the index from the history: + // - if the index is 0, we can change our lastKnownHash + // - otherwise, just push to view + var idx; + if (box.history.some(function (el, i) { + if (hash === el) { + idx = i; + return true; + } + })) { + if (idx === 0) { + m.lastKnownHash = hash; + box.history.shift(); + delete box.content[hash]; + } else if (m.viewed.indexOf(hash) === -1) { + m.viewed.push(hash); + } + } + + // Clear data in memory if needed + // Check the "viewed" array to see if we're able to bump lastKnownhash more + var sliceIdx; + var lastKnownHash; + box.history.some(function (hash, i) { + var isViewed = m.viewed.indexOf(hash); + if (isViewed !== -1) { + sliceIdx = i + 1; + m.viewed.splice(isViewed, 1); + lastKnownHash = hash; + return false; + } + return true; + }); + + if (sliceIdx) { + box.history = box.history.slice(sliceIdx); + m.lastKnownHash = lastKnownHash; + } + + // Make sure we remove data about dismissed messages + Object.keys(box.content).forEach(function (h) { + if (box.history.indexOf(h) === -1 || m.viewed.indexOf(h) !== -1) { + delete box.content[h]; + } + }); + + Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { + cb(); + ctx.emit('VIEWED', { + type: type, + hash: hash + }, ctx.clients.filter(function (clientId) { + return clientId !== cId; + })); + }); + }; + + var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { queue: [], // Store the messages to send when the channel is ready @@ -126,8 +232,19 @@ proxy.mailboxes = { msg: msg, hash: hash }; - box.content[hash] = msg; - showMessage(ctx, type, message); + Handlers(ctx, box, message, function (toDismiss) { + if (toDismiss) { + dismiss(ctx, { + type: type, + hash: hash + }, '', function () { + console.log('Notification handled automatically'); + }); + return; + } + box.content[hash] = msg; + showMessage(ctx, type, message); + }); } else { // Message has already been viewed by the user if (Object.keys(box.content).length === 0) { @@ -182,105 +299,6 @@ proxy.mailboxes = { CpNetflux.start(cfg); }; - // Send a message to someone else - var sendTo = function (ctx, type, msg, user, cb) { - if (!Crypto.Mailbox) { - return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."}); - } - var keys = getMyKeys(ctx); - if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } - if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); } - - var crypto = Crypto.Mailbox.createEncryptor(keys); - var network = ctx.store.network; - - var ciphertext = crypto.encrypt(JSON.stringify({ - type: type, - content: msg - }), user.curvePublic); - - network.join(user.channel).then(function (wc) { - wc.bcast(ciphertext).then(function () { - cb(); - }); - }, function (err) { - cb({error: err}); - }); - }; - - var updateLastKnownHash = function (ctx, type) { - var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); - if (!m) { return; } - var box = ctx.boxes[type]; - if (!box) { return; } - - }; - - // Mark a message as read - var dismiss = function (ctx, data, cId, cb) { - var type = data.type; - var hash = data.hash; - var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); - if (!m) { return void cb({error: 'NOT_FOUND'}); } - var box = ctx.boxes[type]; - if (!box) { return void cb({error: 'NOT_LOADED'}); } - - // If the hash in in our history, get the index from the history: - // - if the index is 0, we can change our lastKnownHash - // - otherwise, just push to view - var idx; - if (box.history.some(function (el, i) { - if (hash === el) { - idx = i; - return true; - } - })) { - if (idx === 0) { - m.lastKnownHash = hash; - box.history.shift(); - delete box.content[hash]; - } else if (m.viewed.indexOf(hash) === -1) { - m.viewed.push(hash); - } - } - - // Clear data in memory if needed - // Check the "viewed" array to see if we're able to bump lastKnownhash more - var sliceIdx; - var lastKnownHash; - box.history.some(function (hash, i) { - var isViewed = m.viewed.indexOf(hash); - if (isViewed !== -1) { - sliceIdx = i + 1; - m.viewed.splice(isViewed, 1); - lastKnownHash = hash; - return false; - } - return true; - }); - - if (sliceIdx) { - box.history = box.history.slice(sliceIdx); - m.lastKnownHash = lastKnownHash; - } - - // Make sure we remove data about dismissed messages - Object.keys(box.content).forEach(function (h) { - if (box.history.indexOf(h) === -1 || m.viewed.indexOf(h) !== -1) { - delete box.content[h]; - } - }); - - Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { - cb(); - ctx.emit('VIEWED', { - type: type, - hash: hash - }, ctx.clients.filter(function (clientId) { - return clientId !== cId; - })); - }); - }; var subscribe = function (ctx, data, cId, cb) { // Get existing notifications @@ -306,10 +324,13 @@ proxy.mailboxes = { ctx.clients.splice(idx, 1); }; - Mailbox.init = function (store, waitFor, emit) { + Mailbox.init = function (cfg, waitFor, emit) { var mailbox = {}; + var store = cfg.store; var ctx = { store: store, + pinPads: cfg.pinPads, + updateMetadata: cfg.updateMetadata, emit: emit, clients: [], boxes: {} @@ -317,7 +338,7 @@ proxy.mailboxes = { var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; - initializeMailboxes(mailboxes); + initializeMailboxes(ctx, mailboxes); Object.keys(mailboxes).forEach(function (key) { if (TYPES.indexOf(key) === -1) { return; } @@ -346,6 +367,10 @@ proxy.mailboxes = { }); }; + mailbox.dismiss = function (data, cb) { + dismiss(ctx, data, '', cb); + }; + mailbox.sendTo = function (type, msg, user, cb) { sendTo(ctx, type, msg, user, cb); }; diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index aed0d5ab4..40c446c48 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -58,8 +58,8 @@ define([ ADD_SHARED_FOLDER: Store.addSharedFolder, LOAD_SHARED_FOLDER: Store.loadSharedFolderAnon, // Messaging - INVITE_FROM_USERLIST: Store.inviteFromUserlist, - ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, + ANSWER_FRIEND_REQUEST: Store.answerFriendRequest, + SEND_FRIEND_REQUEST: Store.sendFriendRequest, // Chat CHAT_COMMAND: Store.messenger.execCommand, // OnlyOffice diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 5c164a3eb..9a5ba5730 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -502,16 +502,11 @@ define([ }); // Messaging - sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) { - Cryptpad.inviteFromUserlist(netfluxId, cb); + sframeChan.on('Q_SEND_FRIEND_REQUEST', function (data, cb) { + Cryptpad.messaging.sendFriendRequest(data, cb); }); - Cryptpad.messaging.onFriendRequest.reg(function (confirmText, cb) { - sframeChan.query('Q_INCOMING_FRIEND_REQUEST', confirmText, function (err, data) { - cb(data); - }); - }); - Cryptpad.messaging.onFriendComplete.reg(function (data) { - sframeChan.event('EV_FRIEND_REQUEST', data); + sframeChan.on('Q_ANSWER_FRIEND_REQUEST', function (data, cb) { + Cryptpad.messaging.answerFriendRequest(data, cb); }); // History @@ -954,9 +949,6 @@ define([ readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), onConnect: function () { - var href = parsed.getUrl(); - // Add friends requests handlers when we have the final href - Cryptpad.messaging.addHandlers(href); if (window.location.hash && window.location.hash !== '#') { /*window.location = parsed.getUrl({ present: parsed.hashData.present, diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 76dcaa029..7081136c0 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -380,14 +380,24 @@ define([ funcs.mergeAnonDrive = function (cb) { ctx.sframeChan.query('Q_MERGE_ANON_DRIVE', null, cb); }; - // Friends - var pendingFriends = []; + + // Create friend request funcs.getPendingFriends = function () { - return pendingFriends.slice(); + return ctx.metadataMgr.getPrivateData().pendingFriends; + }; + funcs.sendFriendRequest = function (data, cb) { + ctx.sframeChan.query('Q_SEND_FRIEND_REQUEST', data, cb); + }; + // Friend requests received + var friendRequests = {}; + funcs.addFriendRequest = function (data) { + var curve = Util.find(data, ['content', 'msg', 'author']); + console.log(data); + console.log(curve); + friendRequests[curve] = data; }; - funcs.sendFriendRequest = function (netfluxId) { - ctx.sframeChan.query('Q_SEND_FRIEND_REQUEST', netfluxId, $.noop); - pendingFriends.push(netfluxId); + funcs.getFriendRequests = function () { + return JSON.parse(JSON.stringify(friendRequests)); }; // Feedback @@ -524,15 +534,6 @@ define([ UI.addTooltips(); - ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) { - UI.confirm(confirmMsg, cb, null, true); - }); - ctx.sframeChan.on('EV_FRIEND_REQUEST', function (data) { - var i = pendingFriends.indexOf(data.netfluxId); - if (i !== -1) { pendingFriends.splice(i, 1); } - UI.log(data.logText); - }); - ctx.sframeChan.on("EV_PAD_PASSWORD", function () { UIElements.displayPasswordPrompt(funcs); }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 8eb3b6c2d..d782622e5 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -8,10 +8,11 @@ define([ '/common/common-util.js', '/common/common-feedback.js', '/common/hyperscript.js', + '/common/notifications.js', '/common/messenger-ui.js', '/customize/messages.js', ], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h, -MessengerUI, Messages) { +Notifications, MessengerUI, Messages) { var Common; var Bar = { @@ -232,7 +233,11 @@ MessengerUI, Messages) { // Display the userlist // Editors - var pendingFriends = Common.getPendingFriends(); + var pendingFriends = Common.getPendingFriends(); // Friend requests sent + var friendRequests = Common.getFriendRequests(); // Friend requests received + console.log(friendRequests); + var friendTo = +new Date() - (2 * 24 * 3600 * 1000); + friendTo = +new Date(); // XXX editUsersNames.forEach(function (data) { var name = data.name || Messages.anonymous; var $span = $('', {'class': 'cp-avatar'}); @@ -300,9 +305,21 @@ MessengerUI, Messages) { } } else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic] && !priv.readOnly) { - if (pendingFriends.indexOf(data.netfluxId) !== -1) { + console.log(pendingFriends); + if (pendingFriends[data.curvePublic] && pendingFriends[data.curvePublic] > friendTo) { $('', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending) .appendTo($rightCol); + } else if (friendRequests[data.curvePublic]) { + $('