From 7f0dd4f5765463de7d1427ef0540fd6f96b16d68 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 11 Sep 2019 18:01:48 +0200 Subject: [PATCH] Load shared folder and register change events in teams --- www/common/drive-ui.js | 24 ++--- www/common/outer/async-store.js | 6 -- www/common/outer/team.js | 165 +++++++++++++++++++++++++------- www/team/inner.js | 22 +++-- www/team/main.js | 17 ++-- 5 files changed, 166 insertions(+), 68 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 1d6b8850c..8cee2eb10 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4316,18 +4316,20 @@ define([ refresh(); UI.removeLoadingScreen(); - sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { - var ids = manager.findChannels(data); - var titles = []; - ids.forEach(function (id) { - var title = manager.getTitle(id); - titles.push(title); - var paths = manager.findFile(id); - manager.delete(paths, refresh); + if (!APP.isTeam) { + sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { + var ids = manager.findChannels(data); + var titles = []; + ids.forEach(function (id) { + var title = manager.getTitle(id); + titles.push(title); + var paths = manager.findFile(id); + manager.delete(paths, refresh); + }); + if (!titles.length) { return; } + UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')])); }); - if (!titles.length) { return; } - UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')])); - }); + } return { refresh: refresh diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index b0e402250..e9a86fca5 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -147,7 +147,6 @@ define([ typeof(store.proxy.curvePublic) === 'string'; }; - // XXX TODO Implement same behavior in team.js var getUserChannelList = function () { // start with your userHash... var userHash = storeHash; @@ -190,12 +189,10 @@ define([ return list; }; - // XXX TODO Implement same behavior in team.js var getExpirableChannelList = function () { return store.manager.getChannelsList('expirable'); }; - // XXX TODO Implement same behavior in team.js var getCanonicalChannelList = function (expirable) { var list = expirable ? getExpirableChannelList() : getUserChannelList(); return Util.deduplicateString(list).sort(); @@ -396,9 +393,6 @@ define([ if (!store.loggedIn) { return cb(); } if (store.rpc) { return void cb(account); } require(['/common/pinpad.js'], function (Pinpad) { - // XXX Teams: we wont' pass the team's proxy directly here because all the users - // may not have access to the edPrivate key - // Users without edPrivate should not be able to create a pinpad object Pinpad.create(store.network, store.proxy, function (e, call) { if (e) { return void cb({error: e}); } diff --git a/www/common/outer/team.js b/www/common/outer/team.js index fb4d876ff..bcaa8ee1b 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -5,6 +5,7 @@ define([ '/common/common-realtime.js', '/common/proxy-manager.js', + '/common/userObject.js', '/common/outer/sharedfolder.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', @@ -13,24 +14,96 @@ define([ '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js', ], function (Util, Hash, Constants, Realtime, - ProxyManager, SF, + ProxyManager, UserObject, SF, Listmap, Crypto, ChainPad, nThen) { var Team = {}; var Nacl = window.nacl; var initializeTeams = function (ctx, cb) { - // XXX ? cb(); }; + var registerChangeEvents = function (ctx, team, proxy, fId) { + if (!team) { return; } + proxy.on('change', [], function (o, n, p) { + if (fId) { + // Pin the new pads + if (p[0] === UserObject.FILES_DATA && typeof(n) === "object" && n.channel && !n.owners) { + var toPin = [n.channel]; + // Also pin the onlyoffice channels if they exist + if (n.rtChannel) { toPin.push(n.rtChannel); } + if (n.lastVersion) { toPin.push(n.lastVersion); } + team.pin(toPin, function (obj) { console.error(obj); }); + } + // Unpin the deleted pads (deleted <=> changed to undefined) + if (p[0] === UserObject.FILES_DATA && typeof(o) === "object" && o.channel && !n) { + var toUnpin = [o.channel]; + var c = team.manager.findChannel(o.channel); + var exists = c.some(function (data) { + return data.fId !== fId; + }); + if (!exists) { // Unpin + // Also unpin the onlyoffice channels if they exist + if (o.rtChannel) { toUnpin.push(o.rtChannel); } + if (o.lastVersion) { toUnpin.push(o.lastVersion); } + team.unpin(toUnpin, function (obj) { console.error(obj); }); + } + } + } + team.sendEvent('DRIVE_CHANGE', { + id: fId, + old: o, + new: n, + path: p + }); + }); + proxy.on('remove', [], function (o, p) { + team.sendEvent('DRIVE_REMOVE', { + id: fId, + old: o, + path: p + }); + }); + }; + + var getTeamChannelList = function (ctx, id) { + // Get the list of pads' channel ID in your drive + // This list is filtered so that it doesn't include pad owned by other users + // It now includes channels from shared folders + var store = ctx.teams[id]; + if (!store) { return null; } + var list = store.manager.getChannelsList('pin'); + + // Teams will always be owned for now + // XXX if the team is not owned, add the teamChannel to the list + /* + var _team = Util.find(ctx, ['store', 'proxy', 'teams', id]); + var secret = Hash.getSecrets('team', _team.hash, _team.password); + var teamChannel = secret.channel; + list.push(userChannel); + */ + + + // XXX Add the team mailbox + /* + if (store.proxy.mailboxes) { + var mList = Object.keys(store.proxy.mailboxes).map(function (m) { + return store.proxy.mailboxes[m].channel; + }); + list = list.concat(mList); + } + */ + + list.sort(); + return list; + }; + var handleSharedFolder = function (ctx, id, sfId, rt) { var t = ctx.teams[id]; if (!t) { return; } t.sharedFolders[sfId] = rt; - // XXX register events - // rt.proxy.on('change',... emit change event - // TODO: pin or unpin document added to a shared folder from someone who is not a member of the team + registerChangeEvents(ctx, t, rt.proxy, sfId); }; var initRpc = function (ctx, team, data, cb) { @@ -41,24 +114,11 @@ define([ if (e) { return void cb(e); } team.rpc = call; cb(); - // XXX get pin limit? }); }); }; - var onReady = function (ctx, id, lm, keys, cb) { - // XXX - // sanity check: do we have all the required keys? - // [x] initialize team rpc with pin, unpin, ... - // [x] team.rpc = rpc - // [x] load manager with userObject - // team.manager =... team.userObject = .... - // [ ] load members pad - // [ ] load shared folders - // [ ] register listmap 'change' events for the drive and the shared folders - // [ ] ~resetPins for the team? - // [ ] getPinLimit - + var onReady = function (ctx, id, lm, keys, cId, cb) { var proxy = lm.proxy; var team = { id: id, @@ -70,20 +130,22 @@ define([ sharedFolders: {}, // equivalent of store.sharedFolders in async-store }; + if (cId) { team.clients.push(cId); } + team.sendEvent = function (q, data, sender) { ctx.emit(q, data, team.clients.filter(function (cId) { return cId !== sender; })); }; - var pin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; - var unpin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; + team.pin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; + team.unpin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; nThen(function (waitFor) { if (!keys.edPrivate) { return; } initRpc(ctx, team, keys, waitFor(function (err) { if (err) { return; } - pin = function (data, cb) { + team.pin = function (data, cb) { if (!team.rpc) { return void cb({error: 'TEAM_RPC_NOT_READY'}); } if (typeof(cb) !== 'function') { console.error('expected a callback'); } team.rpc.pin(data, function (e, hash) { @@ -92,7 +154,7 @@ define([ }); }; - unpin = function (data, cb) { + team.unpin = function (data, cb) { if (!team.rpc) { return void cb({error: 'TEAM_RPC_NOT_READY'}); } if (typeof(cb) !== 'function') { console.error('expected a callback'); } team.rpc.unpin(data, function (e, hash) { @@ -111,8 +173,8 @@ define([ var manager = team.manager = ProxyManager.create(proxy.drive, { onSync: function (cb) { ctx.Store.onSync(id, cb); }, edPublic: proxy.edPublic, - pin: pin, - unpin: unpin, + pin: team.pin, + unpin: team.unpin, loadSharedFolder: loadSharedFolder, settings: { drive: Util.find(ctx.store, ['proxy', 'settings', 'drive']) @@ -142,12 +204,26 @@ define([ team.userObject = manager.user.userObject; team.userObject.fixFiles(); }).nThen(function (waitFor) { - // XXX - // Load shared folders - // Load members pad - console.log('ok', waitFor); - }).nThen(function () { ctx.teams[id] = team; + registerChangeEvents(ctx, team, proxy); + SF.loadSharedFolders(ctx.Store, ctx.store.network, team, team.userObject, waitFor); + // XXX + // Load members pad + }).nThen(function () { + if (!team.rpc) { return; } + var list = getTeamChannelList(ctx, id); + var local = Hash.hashChannelList(list); + // Check pin list + team.rpc.getServerHash(function (e, hash) { + if (e) { return void console.warn(e); } + if (hash !== local) { + // Reset pin list + team.rpc.reset(list, function (e/*, hash*/) { + if (e) { console.warn(e); } + }); + } + }); + }).nThen(function () { if (ctx.onReadyHandlers[id]) { ctx.onReadyHandlers[id].forEach(function (obj) { // Callback and subscribe the client to new notifications @@ -185,7 +261,7 @@ define([ var lm = Listmap.create(cfg); lm.proxy.on('create', function () { }).on('ready', function () { - onReady(ctx, id, lm, teamData.keys, cb); + onReady(ctx, id, lm, teamData.keys, null, cb); }); }; @@ -252,7 +328,7 @@ define([ onReady(ctx, id, lm, { edPrivate: keyPair.secretKey, edPublic: keyPair.publicKey - }, function () { + }, cId, function () { cb(); }); }).on('error', function (info) { @@ -264,6 +340,29 @@ define([ }; var subscribe = function (ctx, id, cId, cb) { + // Unsubscribe from other teams: one tab can only receive events about one team + Object.keys(ctx.teams).forEach(function (teamId) { + var c = ctx.teams[teamId].clients; + var idx = c.indexOf(cId); + if (idx !== -1) { + c.splice(idx, 1); + } + }); + // Also remove from pending subscriptions + Object.keys(ctx.onReadyHandlers).forEach(function (teamId) { + var idx = -1; + ctx.onReadyHandlers[teamId].some(function (obj, _idx) { + if (obj.cId === cId) { + idx = _idx; + return true; + } + }); + if (idx !== -1) { + ctx.onReadyHandlers[teamId].splice(idx, 1); + } + }); + + if (!id) { return; } // If the team is loading, as ourselves in the list if (ctx.onReadyHandlers[id]) { var _idx = ctx.onReadyHandlers[id].indexOf(cId); @@ -276,7 +375,7 @@ define([ return; } // Otherwise, subscribe to new notifications - if (!id || !ctx.teams[id]) { + if (!ctx.teams[id]) { return void cb({error: 'EINVAL'}); } var clients = ctx.teams[id].clients; diff --git a/www/team/inner.js b/www/team/inner.js index 2ce2ce9d9..a5acee042 100644 --- a/www/team/inner.js +++ b/www/team/inner.js @@ -30,7 +30,9 @@ define([ Messages) { var APP = {}; - var driveAPP = {}; + var driveAPP = { + isTeam: true + }; //var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName; var copyObjectValue = function (objRef, objToCopy) { @@ -86,9 +88,11 @@ define([ 'back': { onClick: function (common) { var sframeChan = common.getSframeChannel(); - sframeChan.query('Q_SET_TEAM', null, function (err) { - if (err) { return void console.error(err); } - APP.buildUI(common); + APP.module.execCommand('SUBSCRIBE', null, function () { + sframeChan.query('Q_SET_TEAM', null, function (err) { + if (err) { return void console.error(err); } + APP.buildUI(common); + }); }); } }, @@ -248,9 +252,11 @@ define([ h('li', a) // XXX ]))); $(a).click(function () { - sframeChan.query('Q_SET_TEAM', id, function (err) { - if (err) { return void console.error(err); } - buildUI(common, true); + APP.module.execCommand('SUBSCRIBE', id, function () { + sframeChan.query('Q_SET_TEAM', id, function (err) { + if (err) { return void console.error(err); } + buildUI(common, true); + }); }); }); }); @@ -304,7 +310,7 @@ define([ }); makeBlock('drive', function (common, cb) { - var $div = $('div.cp-team-drive').empty(); + $('div.cp-team-drive').empty(); var content = [ h('div.cp-app-drive-container', {tabindex:0}, [ h('div#cp-app-drive-tree'), diff --git a/www/team/main.js b/www/team/main.js index 9bf8a1296..cbaec4672 100644 --- a/www/team/main.js +++ b/www/team/main.js @@ -38,6 +38,8 @@ define([ }).nThen(function (/*waitFor*/) { var teamId; // XXX var afterSecrets = function (Cryptpad, Utils, secret, cb) { + return void cb(); + /* var hash = window.location.hash.slice(1); if (hash && Utils.LocalStore.isLoggedIn()) { return; // XXX How to add a shared folder? @@ -57,8 +59,9 @@ define([ return void Cryptpad.loadSharedFolder(id, data, cb); } cb(); + */ }; - var addRpc = function (sframeChan, Cryptpad, Utils) { + var addRpc = function (sframeChan, Cryptpad) { sframeChan.on('Q_SET_TEAM', function (data, cb) { teamId = data; cb(); @@ -89,14 +92,8 @@ define([ cb(obj); }); }); - sframeChan.on('EV_DRIVE_SET_HASH', function (hash) { - // Update the hash in the address bar - if (!Utils.LocalStore.isLoggedIn()) { return; } - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = hash || ''; - window.onhashchange = ohc; - ohc({reset:true}); + sframeChan.on('EV_DRIVE_SET_HASH', function () { + return; }); Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); @@ -108,7 +105,7 @@ define([ // Intercept events for the team drive and send them the required way if (obj.type !== 'team' || ['DRIVE_CHANGE', 'DRIVE_LOG', 'DRIVE_REMOVE'].indexOf(obj.data.ev) === -1) { return; } - sframeChan.event(obj.data.ev, obj.data.data); + sframeChan.event('EV_'+obj.data.ev, obj.data.data); }); }; SFCommonO.start({