From e47dd9286f20438dceb48dd5e9b4707fd2aae8d5 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Wed, 25 Sep 2019 17:15:39 -0500 Subject: [PATCH] add Undo support to unfollow --- pub/activity.js | 28 ++++++++++++++++++++++------ pub/federation.js | 2 +- routes/inbox.js | 30 ++++++++++++++++++++++-------- store/stream.js | 6 ++++++ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/pub/activity.js b/pub/activity.js index df53542..c49efa8 100644 --- a/pub/activity.js +++ b/pub/activity.js @@ -7,7 +7,8 @@ const pubFederation = require('./federation') module.exports = { address, addToOutbox, - build + build, + undo } function build (type, actorId, object, to, cc, etc) { @@ -61,11 +62,26 @@ async function address (activity) { } function addToOutbox (actor, activity) { - return Promise.all([ - // ensure object is cached, but don't alter representation in activity - // so activities can be sent with objects as links - //pubObject.resolve(activity.object), + const tasks = [ store.stream.save(activity), address(activity).then(addresses => pubFederation.deliver(actor, activity, addresses)) - ]) + ] + // ensure activity object is cached if local, but do not try to resolve links + // because Mastodon won't resolve activity IRIs + if (pubUtils.validateObject(activity.object)) { + tasks.push(pubObject.resolve(activity.object)) + } + return Promise.all(tasks) +} + +function undo (activity, undoActor) { + if (!pubUtils.validateActivity(activity)) { + if (!activity || Object.prototype.toString.call(activity) !== '[object String]') { + throw new Error('Invalid undo target') + } + activity = { id: activity } + } + // matches the target activity with the actor from the undo + // so actors can only undo their own activities + return store.stream.remove(activity, undoActor) } diff --git a/pub/federation.js b/pub/federation.js index 0c671cd..13441c4 100644 --- a/pub/federation.js +++ b/pub/federation.js @@ -24,7 +24,7 @@ function deliver (actor, activity, addresses) { delete activity.bcc } const requests = addresses.map(addr => { - console.log(`delivering to${addr}`) + console.log(`delivering to ${addr}`) return request({ method: 'POST', url: addr, diff --git a/routes/inbox.js b/routes/inbox.js index f4de2b5..6fe8c89 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -6,14 +6,19 @@ const store = require('../store') router.post('/', net.validators.activity, net.security.verifySignature, function (req, res) { req.body._meta = { _target: pub.utils.usernameToIRI(req.user) } - console.log(req.body); + console.log(req.body) + const toDo = { + saveActivity: true, + saveObject: false + } // side effects switch (req.body.type) { case 'Accept': // TODO - side effect necessary for following collection? break case 'Follow': - //req.body._meta._target = req.body.object.id + // TODO resolve object and ensure specified target matches inbox user + // req.body._meta._target = req.body.object.id // send acceptance reply pub.actor.getOrCreateActor(req.user, true) .then(user => { @@ -24,6 +29,7 @@ router.post('/', net.validators.activity, net.security.verifySignature, function .catch(e => console.log(e)) break case 'Create': + toDo.saveObject = true pub.actor.getOrCreateActor(req.user, true) .then(user => { const to = [user.followers] @@ -31,15 +37,23 @@ router.post('/', net.validators.activity, net.security.verifySignature, function pub.utils.actorFromActivity(req.body), 'https://www.w3.org/ns/activitystreams#Public' ] - const accept = pub.activity.build('Announce', user.id, req.body.object.id, to, cc) - return pub.activity.addToOutbox(user, accept) + const announce = pub.activity.build('Announce', user.id, req.body.object.id, to, cc) + return pub.activity.addToOutbox(user, announce) }).catch(e => console.log(e)) break + case 'Undo': + pub.activity.undo(req.body.object, req.body.actor) + .catch(err => console.log(err)) + break } - Promise.all([ - pub.object.resolve(req.body.object), - store.stream.save(req.body) - ]).then(() => res.status(200).send()) + const tasks = [] + if (toDo.saveObject) { + tasks.push(pub.object.resolve(req.body.object)) + } + if (toDo.saveActivity) { + tasks.push(store.stream.save(req.body)) + } + Promise.all(tasks).then(() => res.status(200).send()) .catch(err => { console.log(err) res.status(500).send() diff --git a/store/stream.js b/store/stream.js index c603544..c153299 100644 --- a/store/stream.js +++ b/store/stream.js @@ -2,6 +2,7 @@ const connection = require('./connection') module.exports = { get, + remove, save } @@ -34,3 +35,8 @@ async function save (activity) { // server object ID avoids mutating local copy of document .insertOne(activity, { forceServerObjectId: true }) } + +function remove (activity, actor) { + return connection.getDb().collection('streams') + .deleteMany({ id: activity.id, actor: actor }) +}