diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index fef46951c..0311387ee 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -693,6 +693,13 @@ define([ pad.onConnectEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent(); + pad.requestAccess = function (data, cb) { + postMessage("REQUEST_PAD_ACCESS", data, cb); + }; + pad.giveAccess = function (data, cb) { + postMessage("GIVE_PAD_ACCESS", data, cb); + }; + common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) { if (!href) { return void cb({ error: 'EINVAL_HREF' }); } var parsed = Hash.parsePadUrl(href); diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index 9247656e1..aa6e57b0a 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -49,7 +49,7 @@ define([ // We want to merge an edit pad: check if we have the same channel // but read-only and upgrade it in that case datas.forEach(function (pad) { - if (!pad.href) { data.href = pad.href; } + if (pad.data && !pad.data.href) { pad.data.href = data.href; } }); return; } diff --git a/www/common/notifications.js b/www/common/notifications.js index 20352876c..4a3f7ca19 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -2,9 +2,10 @@ define([ 'jquery', '/common/hyperscript.js', '/common/common-hash.js', + '/common/common-interface.js', '/common/common-ui-elements.js', '/customize/messages.js', -], function ($, h, Hash, UIElements, Messages) { +], function ($, h, Hash, UI, UIElements, Messages) { var handlers = {}; @@ -78,7 +79,10 @@ define([ return Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title]); }; content.handler = function () { - var todo = function () { common.openURL(msg.content.href); }; + var todo = function () { + common.openURL(msg.content.href); + defaultDismiss(common, data)(); + }; if (!msg.content.password) { return void todo(); } common.getSframeChannel().query('Q_SESSIONSTORAGE_PUT', { key: 'newPadPassword', @@ -100,11 +104,58 @@ define([ common.openURL('/support/'); defaultDismiss(common, data)(); }; + }; + + handlers['REQUEST_PAD_ACCESS'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Check authenticity + if (msg.author !== msg.content.user.curvePublic) { return; } + + // Display the notification + content.getFormatText = function () { + return 'Edit access request: ' + msg.content.title + ' - ' + msg.content.user.displayName; + }; // XXX + + // if not archived, add handlers + content.handler = function () { + UI.confirm("Give edit rights?", function (yes) { + if (!yes) { return; } + common.getSframeChannel().event('EV_GIVE_ACCESS', { + channel: msg.content.channel, + user: msg.content.user + }); + defaultDismiss(common, data)(); + }); + }; + if (!content.archived) { content.dismissHandler = defaultDismiss(common, data); } }; + handlers['GIVE_PAD_ACCESS'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Check authenticity + if (msg.author !== msg.content.user.curvePublic) { return; } + + if (!msg.content.href) { return; } + + // Display the notification + content.getFormatText = function () { + return 'Edit access received: ' + msg.content.title + ' from ' + msg.content.user.displayName; + }; // XXX + + // if not archived, add handlers + content.handler = function () { + common.openURL(msg.content.href); + defaultDismiss(common, data)(); + }; + }; + return { add: function (common, data) { var type = data.content.msg.type; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index fb1bcd202..ca7e4cd7c 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1090,6 +1090,7 @@ define([ var channels = Store.channels = store.channels = {}; Store.joinPad = function (clientId, data) { + console.log('joining', data.channel); var isNew = typeof channels[data.channel] === "undefined"; var channel = channels[data.channel] = channels[data.channel] || { queue: [], @@ -1243,6 +1244,97 @@ define([ channel.sendMessage(msg, clientId, cb); }; + Store.requestPadAccess = function (clientId, data, cb) { + // Get owners from pad metadata + // Try to find an owner in our friend list + // Mailbox... + var channel = channels[data.channel]; + if (!data.send && channel && (!channel.data || !channel.data.channel)) { + var i = 0; + var it = setInterval(function () { + if (channel.data && channel.data.channel) { + clearInterval(it); + Store.requestPadAccess(clientId, data, cb); + return; + } + if (i >= 300) { // One minute timeout + clearInterval(it); + } + i++; + }, 200); + return; + } + var fData = channel.data || {}; + if (fData.owners) { + var friends = store.proxy.friends || {}; + if (Object.keys(friends).length > 1) { + var owner; + fData.owners.some(function (edPublic) { + return Object.keys(friends).some(function (curve) { + if (curve === "me") { return; } + if (edPublic === friends[curve].edPublic && + friends[curve].notifications) { + owner = friends[curve]; + return true; + } + }); + }); + if (owner) { + if (data.send) { + var myData = Messaging.createData(store.proxy); + delete myData.channel; + store.mailbox.sendTo('REQUEST_PAD_ACCESS', { + channel: data.channel, + user: myData + }, { + channel: owner.notifications, + curvePublic: owner.curvePublic + }, function () { + cb({state: true}); + }); + return; + } + return void cb({state: true}); + } + } + } + cb({sent: false}); + }; + Store.givePadAccess = function (clientId, data, cb) { + var edPublic = store.proxy.edPublic; + var channel = data.channel; + var res = store.manager.findChannel(channel); + + if (!data.user || !data.user.notifications || !data.user.curvePublic) { + return void cb({error: 'EINVAL'}); + } + + var href, title; + + if (!res.some(function (obj) { + if (obj.data && + Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 && + obj.data.href) { + href = obj.data.href; + title = obj.data.title; + return true; + } + })) { return void cb({error: 'ENOTFOUND'}); } + + var myData = Messaging.createData(store.proxy); + delete myData.channel; + store.mailbox.sendTo("GIVE_PAD_ACCESS", { + channel: channel, + href: href, + title: title, + user: myData + }, { + channel: data.user.notifications, + curvePublic: data.user.curvePublic + }); + cb(); + }; + // GET_FULL_HISTORY from sframe-common-outer Store.getFullHistory = function (clientId, data, cb) { var network = store.network; diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 6aa919112..6bc0536be 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -209,6 +209,54 @@ define([ cb(); }; + // Incoming edit rights request: add data before sending it to inner + handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + + if (msg.author !== content.user.curvePublic) { return void cb(true); } + + var channel = content.channel; + var res = ctx.store.manager.findChannel(channel); + + if (!res.length) { return void cb(true); } + + var edPublic = ctx.store.proxy.edPublic; + var title; + if (!res.some(function (obj) { + if (obj.data && + Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 && + obj.data.href) { + title = obj.data.filename || obj.data.title; + return true; + } + })) { return void cb(true); } + + content.title = title; + cb(false); + }; + + handlers['GIVE_PAD_ACCESS'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + + if (msg.author !== content.user.curvePublic) { return void cb(true); } + + var channel = content.channel; + var res = ctx.store.manager.findChannel(channel); + + var title; + res.forEach(function (obj) { + if (obj.data && !obj.data.href) { + if (!title) { title = obj.data.filename || obj.data.title; } + obj.data.href = content.href; + } + }); + + content.title = title || content.title; + cb(false); + }; + return { add: function (ctx, box, data, cb) { /** diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 517bea017..24d14345f 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -339,9 +339,9 @@ proxy.mailboxes = { var txid = parsed[1]; var req = ctx.req[txid]; + if (!req) { return; } var type = parsed[0]; var _msg = parsed[2]; - if (!req) { return; } var box = req.box; if (type === 'HISTORY_RANGE') { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 6b774339c..bb8e0a674 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -78,6 +78,8 @@ define([ GET_FULL_HISTORY: Store.getFullHistory, GET_HISTORY_RANGE: Store.getHistoryRange, IS_NEW_CHANNEL: Store.isNewChannel, + REQUEST_PAD_ACCESS: Store.requestPadAccess, + GIVE_PAD_ACCESS: Store.givePadAccess, // Drive DRIVE_USEROBJECT: Store.userObjectCommand, // Settings, diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index f1068a6de..af856d1d7 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -603,6 +603,7 @@ define([ 'newpad', 'share', 'limit', + 'request', 'unpinnedWarning', 'notifications' ], diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index fba08279a..81ea43d81 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -941,6 +941,19 @@ define([ sframeChan.event('EV_WORKER_TIMEOUT'); }); + sframeChan.on('EV_GIVE_ACCESS', function (data, cb) { + Cryptpad.padRpc.giveAccess(data, cb); + }); + sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) { + if (readOnly && hashes.editHash) { + return void cb({error: 'ALREADYKNOWN'}); + } + Cryptpad.padRpc.requestAccess({ + send: data, + channel: secret.channel + }, cb); + }); + if (cfg.messaging) { Notifier.getPermission(); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index d6b116df3..53aa93f4f 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -574,6 +574,42 @@ MessengerUI, Messages) { return $shareBlock; }; + var createRequest = function (toolbar, config) { + console.error('test'); + if (!config.metadataMgr) { + throw new Error("You must provide a `metadataMgr` to display the request access button"); + } + + // We can only requets more access if we're in read-only mode + if (config.readOnly !== 1) { return; } + + var $requestBlock = $('