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); }); } }; });