Ability to send/recv public messages in the AP shared inbox areaTag
* Optional subjects * Resolving followers * Various cleanup and tidy
This commit is contained in:
parent
36ebda5269
commit
0402de7444
|
@ -49,12 +49,25 @@ module.exports = class Activity extends ActivityPubObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
static makeCreate(webServer, actor, obj) {
|
static makeCreate(webServer, actor, obj) {
|
||||||
return new Activity({
|
const activity = new Activity({
|
||||||
id: Activity.activityObjectId(webServer),
|
id: Activity.activityObjectId(webServer),
|
||||||
|
to: obj.to,
|
||||||
type: WellKnownActivity.Create,
|
type: WellKnownActivity.Create,
|
||||||
actor,
|
actor,
|
||||||
object: obj,
|
object: obj,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const copy = n => {
|
||||||
|
if (obj[n]) {
|
||||||
|
activity[n] = obj[n];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
copy('to');
|
||||||
|
copy('cc');
|
||||||
|
// :TODO: Others?
|
||||||
|
|
||||||
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
static makeTombstone(obj) {
|
static makeTombstone(obj) {
|
||||||
|
@ -68,7 +81,7 @@ module.exports = class Activity extends ActivityPubObject {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTo(actorUrl, fromUser, webServer, cb) {
|
sendTo(inboxEndpoint, fromUser, webServer, cb) {
|
||||||
const privateKey = fromUser.getProperty(UserProps.PrivateActivityPubSigningKey);
|
const privateKey = fromUser.getProperty(UserProps.PrivateActivityPubSigningKey);
|
||||||
if (_.isEmpty(privateKey)) {
|
if (_.isEmpty(privateKey)) {
|
||||||
return cb(
|
return cb(
|
||||||
|
@ -91,7 +104,7 @@ module.exports = class Activity extends ActivityPubObject {
|
||||||
};
|
};
|
||||||
|
|
||||||
const activityJson = JSON.stringify(this);
|
const activityJson = JSON.stringify(this);
|
||||||
return postJson(actorUrl, activityJson, reqOpts, cb);
|
return postJson(inboxEndpoint, activityJson, reqOpts, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: we need dp/support a bit more here...
|
// :TODO: we need dp/support a bit more here...
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { getJson } = require('../http_util');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const { isString } = require('lodash');
|
const { isString } = require('lodash');
|
||||||
|
const Log = require('../logger');
|
||||||
|
|
||||||
module.exports = class Collection extends ActivityPubObject {
|
module.exports = class Collection extends ActivityPubObject {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
|
@ -260,18 +261,33 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = entries || [];
|
try {
|
||||||
|
entries = (entries || []).map(e => JSON.parse(e.object_json));
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(`Collection "${collectionId}" error: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (mapper && entries.length > 0) {
|
if (mapper && entries.length > 0) {
|
||||||
entries = entries.map(mapper);
|
entries = entries.map(mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = {
|
let obj;
|
||||||
|
if ('all' === page) {
|
||||||
|
obj = {
|
||||||
|
id: collectionId,
|
||||||
|
type: 'OrderedCollection',
|
||||||
|
totalItems: entries.length,
|
||||||
|
orderedItems: entries,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
obj = {
|
||||||
id: `${collectionId}/page=${page}`,
|
id: `${collectionId}/page=${page}`,
|
||||||
type: 'OrderedCollectionPage',
|
type: 'OrderedCollectionPage',
|
||||||
totalItems: entries.length,
|
totalItems: entries.length,
|
||||||
orderedItems: entries,
|
orderedItems: entries,
|
||||||
partOf: collectionId,
|
partOf: collectionId,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return cb(null, new Collection(obj));
|
return cb(null, new Collection(obj));
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A local Message bound for ActivityPub
|
// A local Message bound for ActivityPub
|
||||||
static fromLocalOutgoingMessage(message, webServer, cb) {
|
static fromLocalMessage(message, webServer, cb) {
|
||||||
const localUserId = message.getLocalFromUserId();
|
const localUserId = message.getLocalFromUserId();
|
||||||
if (!localUserId) {
|
if (!localUserId) {
|
||||||
return cb(Errors.UnexpectedState('Invalid user ID for local user!'));
|
return cb(Errors.UnexpectedState('Invalid user ID for local user!'));
|
||||||
|
@ -63,7 +63,7 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteActorAccount = message.getRemoteToUser();
|
const remoteActorAccount = message.getRemoteToUser();
|
||||||
if (!remoteActorAccount) {
|
if (!remoteActorAccount && message.isPrivate()) {
|
||||||
return cb(
|
return cb(
|
||||||
Errors.UnexpectedState('Message does not contain a remote address')
|
Errors.UnexpectedState('Message does not contain a remote address')
|
||||||
);
|
);
|
||||||
|
@ -80,9 +80,13 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
(fromUser, fromActor, callback) => {
|
(fromUser, fromActor, callback) => {
|
||||||
|
if (message.isPrivate()) {
|
||||||
Actor.fromId(remoteActorAccount, (err, remoteActor) => {
|
Actor.fromId(remoteActorAccount, (err, remoteActor) => {
|
||||||
return callback(err, fromUser, fromActor, remoteActor);
|
return callback(err, fromUser, fromActor, remoteActor);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return callback(null, fromUser, fromActor, null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(fromUser, fromActor, remoteActor, callback) => {
|
(fromUser, fromActor, remoteActor, callback) => {
|
||||||
if (!message.replyToMsgId) {
|
if (!message.replyToMsgId) {
|
||||||
|
|
|
@ -904,10 +904,20 @@ module.exports = () => {
|
||||||
name: 'System Bulletins',
|
name: 'System Bulletins',
|
||||||
desc: 'Bulletin messages for all users',
|
desc: 'Bulletin messages for all users',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
activitypub_shared_inbox: {
|
activity_pub: {
|
||||||
|
name: 'ActivityPub Shared Inbox',
|
||||||
|
desc: 'Public and shared ActivityPub messages',
|
||||||
|
|
||||||
|
areas: {
|
||||||
|
activitypub_shared: {
|
||||||
name: 'ActivityPub sharedInbox',
|
name: 'ActivityPub sharedInbox',
|
||||||
desc: 'Public shared inbox for ActivityPub',
|
desc: 'Public shared inbox for ActivityPub',
|
||||||
|
alwaysExportExternal: true,
|
||||||
|
subjectOptional: true,
|
||||||
|
addressFlavor: 'activitypub',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
22
core/fse.js
22
core/fse.js
|
@ -174,6 +174,12 @@ exports.FullScreenEditorModule =
|
||||||
) {
|
) {
|
||||||
// Ignore validation errors if this is the subject field
|
// Ignore validation errors if this is the subject field
|
||||||
// and it's optional
|
// and it's optional
|
||||||
|
const areaInfo = getMessageAreaByTag(this.messageAreaTag);
|
||||||
|
if (true === areaInfo.subjectOptional) {
|
||||||
|
return cb(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// private messages are a little different...
|
||||||
const toView = this.getView('header', MciViewIds.header.to);
|
const toView = this.getView('header', MciViewIds.header.to);
|
||||||
const msgInfo = messageInfoFromAddressedToInfo(
|
const msgInfo = messageInfoFromAddressedToInfo(
|
||||||
getAddressedToInfo(toView.getData())
|
getAddressedToInfo(toView.getData())
|
||||||
|
@ -594,7 +600,11 @@ exports.FullScreenEditorModule =
|
||||||
function populateLocalUserInfo(callback) {
|
function populateLocalUserInfo(callback) {
|
||||||
self.message.setLocalFromUserId(self.client.user.userId);
|
self.message.setLocalFromUserId(self.client.user.userId);
|
||||||
|
|
||||||
if (!self.isPrivateMail()) {
|
const areaInfo = getMessageAreaByTag(self.messageAreaTag);
|
||||||
|
if (
|
||||||
|
!self.isPrivateMail() &&
|
||||||
|
true !== areaInfo.alwaysExportExternal
|
||||||
|
) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,9 +646,19 @@ exports.FullScreenEditorModule =
|
||||||
self.message.toUserName,
|
self.message.toUserName,
|
||||||
(err, toUserId) => {
|
(err, toUserId) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
if (self.message.isPrivate()) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (areaInfo.addressFlavor) {
|
||||||
|
self.message.setExternalFlavor(
|
||||||
|
areaInfo.addressFlavor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
self.message.setLocalToUserId(toUserId);
|
self.message.setLocalToUserId(toUserId);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@ const WellKnownAreaTags = {
|
||||||
Invalid: '',
|
Invalid: '',
|
||||||
Private: 'private_mail',
|
Private: 'private_mail',
|
||||||
Bulletin: 'local_bulletin',
|
Bulletin: 'local_bulletin',
|
||||||
ActivityPubSharedInbox: 'activitypub_shared_inbox',
|
ActivityPubShared: 'activitypub_shared', // sharedInbox -> HERE -> exported as replies (direct) and outbox items (new posts)
|
||||||
};
|
};
|
||||||
exports.WellKnownAreaTags = WellKnownAreaTags;
|
exports.WellKnownAreaTags = WellKnownAreaTags;
|
||||||
|
|
||||||
|
const WellKnownExternalAreaTags = [WellKnownAreaTags.ActivityPubShared];
|
||||||
|
exports.WellKnownExternalAreaTags = WellKnownExternalAreaTags;
|
||||||
|
|
||||||
const WellKnownMetaCategories = {
|
const WellKnownMetaCategories = {
|
||||||
System: 'System',
|
System: 'System',
|
||||||
FtnProperty: 'FtnProperty',
|
FtnProperty: 'FtnProperty',
|
||||||
|
|
|
@ -3,12 +3,18 @@ const Message = require('../message');
|
||||||
const { MessageScanTossModule } = require('../msg_scan_toss_module');
|
const { MessageScanTossModule } = require('../msg_scan_toss_module');
|
||||||
const { getServer } = require('../listening_server');
|
const { getServer } = require('../listening_server');
|
||||||
const Log = require('../logger').log;
|
const Log = require('../logger').log;
|
||||||
|
const { WellKnownAreaTags, AddressFlavor } = require('../message_const');
|
||||||
|
const { Errors } = require('../enig_error');
|
||||||
|
const Collection = require('../activitypub/collection');
|
||||||
|
const Note = require('../activitypub/note');
|
||||||
|
const { makeUserUrl } = require('../activitypub/util');
|
||||||
|
const { getAddressedToInfo } = require('../mail_util');
|
||||||
|
const { PublicCollectionId } = require('../activitypub/const');
|
||||||
|
const Actor = require('../activitypub/actor');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Collection = require('../activitypub/collection');
|
|
||||||
const Note = require('../activitypub/note');
|
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name: 'ActivityPub',
|
name: 'ActivityPub',
|
||||||
|
@ -40,19 +46,83 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private:
|
||||||
|
// Send Note directly to another remote Actor's inbox
|
||||||
|
//
|
||||||
|
// Public:
|
||||||
|
// - The original message may be addressed to a non-ActivityPub address
|
||||||
|
// or something like "All" or "Public"; In this case, ignore that entry
|
||||||
|
// - Additionally, we need to send to the local Actor's followers via their sharedInbox
|
||||||
|
//
|
||||||
|
// To achieve the above for Public, we'll collect the followers from the local
|
||||||
|
// user, query their unique shared inboxes's, update the Note's addressing,
|
||||||
|
// then deliver and store.
|
||||||
|
//
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
callback => {
|
callback => {
|
||||||
Note.fromLocalOutgoingMessage(
|
// Private or addressed to a single AP Actor:
|
||||||
message,
|
Note.fromLocalMessage(message, this._webServer(), (err, noteInfo) => {
|
||||||
this._webServer(),
|
|
||||||
(err, noteInfo) => {
|
|
||||||
return callback(err, noteInfo);
|
return callback(err, noteInfo);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(noteInfo, callback) => {
|
||||||
|
if (message.isPrivate()) {
|
||||||
|
if (!noteInfo.remoteActor) {
|
||||||
|
return callback(
|
||||||
|
Errors.UnexpectedState(
|
||||||
|
'Private messages should contain a remote Actor!'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return callback(null, noteInfo, [noteInfo.remoteActor.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public: we need to build a list of sharedInbox's
|
||||||
|
this._collectDeliveryEndpoints(
|
||||||
|
message,
|
||||||
|
noteInfo.fromUser,
|
||||||
|
(err, deliveryEndpoints) => {
|
||||||
|
return callback(err, noteInfo, deliveryEndpoints);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(noteInfo, callback) => {
|
(noteInfo, deliveryEndpoints, callback) => {
|
||||||
const { note, fromUser, remoteActor } = noteInfo;
|
const { note, fromUser } = noteInfo;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Update the Note's addressing:
|
||||||
|
// - Private:
|
||||||
|
// to: sharedInboxEndpoints[0]
|
||||||
|
// - Public:
|
||||||
|
// to: https://www.w3.org/ns/activitystreams#Public
|
||||||
|
// ... and the message.getRemoteToUser() value *if*
|
||||||
|
// the flavor is deemed ActivityPub
|
||||||
|
// cc: [sharedInboxEndpoints]
|
||||||
|
//
|
||||||
|
if (message.isPrivate()) {
|
||||||
|
note.to = deliveryEndpoints.sharedInboxes[0];
|
||||||
|
} else {
|
||||||
|
if (deliveryEndpoints.additionalTo) {
|
||||||
|
note.to = [
|
||||||
|
PublicCollectionId,
|
||||||
|
deliveryEndpoints.additionalTo,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
note.to = PublicCollectionId;
|
||||||
|
}
|
||||||
|
note.cc = [
|
||||||
|
deliveryEndpoints.followers,
|
||||||
|
...deliveryEndpoints.sharedInboxes,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (note.to.length < 2 && note.cc.length < 2) {
|
||||||
|
// If we only have a generic 'followers' endpoint, there is no where to send to
|
||||||
|
return callback(null, activity, fromUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const activity = Activity.makeCreate(
|
const activity = Activity.makeCreate(
|
||||||
this._webServer(),
|
this._webServer(),
|
||||||
|
@ -60,12 +130,16 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
note
|
note
|
||||||
);
|
);
|
||||||
|
|
||||||
// :TODO: Implement retry logic (connection issues, retryable HTTP status) ??
|
let allEndpoints = deliveryEndpoints.sharedInboxes;
|
||||||
const inbox = remoteActor.inbox;
|
if (deliveryEndpoints.additionalTo) {
|
||||||
|
allEndpoints.push(deliveryEndpoints.additionalTo);
|
||||||
// const inbox = remoteActor.endpoints.sharedInbox;
|
}
|
||||||
// activity.object.to = 'https://www.w3.org/ns/activitystreams#Public';
|
allEndpoints = Array.from(new Set(allEndpoints)); // unique again
|
||||||
|
|
||||||
|
async.eachLimit(
|
||||||
|
allEndpoints,
|
||||||
|
4,
|
||||||
|
(inbox, nextInbox) => {
|
||||||
activity.sendTo(
|
activity.sendTo(
|
||||||
inbox,
|
inbox,
|
||||||
fromUser,
|
fromUser,
|
||||||
|
@ -73,13 +147,24 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
(err, respBody, res) => {
|
(err, respBody, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{ error: err.message, inbox: remoteActor.inbox },
|
{
|
||||||
|
inbox,
|
||||||
|
error: err.message,
|
||||||
|
},
|
||||||
'Failed to send "Note" Activity to Inbox'
|
'Failed to send "Note" Activity to Inbox'
|
||||||
);
|
);
|
||||||
} else if (res.statusCode !== 202 && res.statusCode !== 200) {
|
} else if (
|
||||||
|
res.statusCode === 200 ||
|
||||||
|
res.statusCode === 202
|
||||||
|
) {
|
||||||
|
this.log.debug(
|
||||||
|
{ inbox, uuid: message.uuid },
|
||||||
|
'Message delivered to Inbox'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{
|
{
|
||||||
inbox: remoteActor.inbox,
|
inbox,
|
||||||
statusCode: res.statusCode,
|
statusCode: res.statusCode,
|
||||||
body: _.truncate(respBody, 128),
|
body: _.truncate(respBody, 128),
|
||||||
},
|
},
|
||||||
|
@ -87,14 +172,17 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// carry on regardless if we sent and record
|
// If we can't send now, no harm, we'll record to the outbox
|
||||||
// the item in the user's Outbox collection
|
return nextInbox(null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
return callback(null, activity, fromUser);
|
return callback(null, activity, fromUser);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(activity, fromUser, callback) => {
|
(activity, fromUser, callback) => {
|
||||||
// If we failed to send above,
|
|
||||||
Collection.addOutboxItem(
|
Collection.addOutboxItem(
|
||||||
fromUser,
|
fromUser,
|
||||||
activity,
|
activity,
|
||||||
|
@ -166,6 +254,90 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_collectDeliveryEndpoints(message, localUser, cb) {
|
||||||
|
this._collectFollowersSharedInboxEndpoints(
|
||||||
|
localUser,
|
||||||
|
(err, endpoints, followersEndpoint) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Don't inspect the remote address/remote to
|
||||||
|
// Here; We already know this in a public
|
||||||
|
// area. Instead, see if the user typed in
|
||||||
|
// a reasonable AP address here. If so, we'll
|
||||||
|
// try to send directly to them as well.
|
||||||
|
//
|
||||||
|
const addrInfo = getAddressedToInfo(message.toUserName);
|
||||||
|
if (
|
||||||
|
!message.isPrivate() &&
|
||||||
|
AddressFlavor.ActivityPub === addrInfo.flavor
|
||||||
|
) {
|
||||||
|
Actor.fromId(addrInfo.remote, (err, actor) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(null, {
|
||||||
|
additionalTo: actor.id,
|
||||||
|
sharedInboxes: endpoints,
|
||||||
|
followers: followersEndpoint,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb(null, {
|
||||||
|
sharedInboxes: endpoints,
|
||||||
|
followers: followersEndpoint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_collectFollowersSharedInboxEndpoints(localUser, cb) {
|
||||||
|
const localFollowersEndpoint =
|
||||||
|
makeUserUrl(this._webServer(), localUser, '/ap/users/') + '/followers';
|
||||||
|
|
||||||
|
Collection.followers(localFollowersEndpoint, 'all', (err, collection) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collection.orderedItems || collection.orderedItems.length < 1) {
|
||||||
|
// no followers :(
|
||||||
|
return cb(null, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.mapLimit(
|
||||||
|
collection.orderedItems,
|
||||||
|
4,
|
||||||
|
(actorId, nextActorId) => {
|
||||||
|
Actor.fromId(actorId, (err, actor) => {
|
||||||
|
return nextActorId(err, actor);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err, followerActors) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedInboxEndpoints = Array.from(
|
||||||
|
new Set(
|
||||||
|
followerActors
|
||||||
|
.map(actor => {
|
||||||
|
return _.get(actor, 'endpoints.sharedInbox');
|
||||||
|
})
|
||||||
|
.filter(inbox => inbox) // drop nulls
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return cb(null, sharedInboxEndpoints, localFollowersEndpoint);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_isEnabled() {
|
_isEnabled() {
|
||||||
// :TODO: check config to see if AP integration is enabled/etc.
|
// :TODO: check config to see if AP integration is enabled/etc.
|
||||||
return this._webServer();
|
return this._webServer();
|
||||||
|
@ -183,7 +355,13 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: Implement the area mapping check for public
|
// Public items do not need a specific 'to'; we'll record to the
|
||||||
|
// local Actor's outbox and send to any followers we know about
|
||||||
|
if (message.areaTag === WellKnownAreaTags.ActivityPubShared) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: Implement the area mapping check for public 'groups'
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -326,6 +326,14 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok(resp, body = '', headers = { 'Content-Type:': 'text/html' }) {
|
||||||
|
if (body && !headers['Content-Length']) {
|
||||||
|
headers['Content-Length'] = Buffer(body).length;
|
||||||
|
}
|
||||||
|
resp.writeHead(200, 'OK', body ? headers : null);
|
||||||
|
return resp.end(body);
|
||||||
|
}
|
||||||
|
|
||||||
created(resp, body = '', headers = { 'Content-Type:': 'text/html' }) {
|
created(resp, body = '', headers = { 'Content-Type:': 'text/html' }) {
|
||||||
resp.writeHead(201, 'Created', body ? headers : null);
|
resp.writeHead(201, 'Created', body ? headers : null);
|
||||||
return resp.end(body);
|
return resp.end(body);
|
||||||
|
|
|
@ -189,20 +189,14 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
req.on('end', () => {
|
req.on('end', () => {
|
||||||
const activity = Activity.fromJsonString(Buffer.concat(body).toString());
|
const activity = Activity.fromJsonString(Buffer.concat(body).toString());
|
||||||
if (!activity) {
|
if (!activity || !activity.isValid()) {
|
||||||
this.log.error(
|
this.log.error(
|
||||||
{ url: req.url, method: req.method, endpoint: 'inbox' },
|
{ url: req.url, method: req.method, endpoint: 'inbox' },
|
||||||
'Failed to parse Activity'
|
|
||||||
);
|
|
||||||
return this.webServer.resourceNotFound(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activity.isValid()) {
|
|
||||||
this.log.warn(
|
|
||||||
{ activity, endpoint: 'inbox' },
|
|
||||||
'Invalid or unsupported Activity'
|
'Invalid or unsupported Activity'
|
||||||
);
|
);
|
||||||
return this.webServer.badRequest(resp);
|
return activity
|
||||||
|
? this.webServer.badRequest(resp)
|
||||||
|
: this.webServer.notImplemented(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (activity.type) {
|
switch (activity.type) {
|
||||||
|
@ -247,7 +241,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.webServer.resourceNotFound(resp);
|
return this.webServer.notImplemented(resp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,20 +253,14 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
req.on('end', () => {
|
req.on('end', () => {
|
||||||
const activity = Activity.fromJsonString(Buffer.concat(body).toString());
|
const activity = Activity.fromJsonString(Buffer.concat(body).toString());
|
||||||
if (!activity) {
|
if (!activity || !activity.isValid()) {
|
||||||
this.log.error(
|
this.log.error(
|
||||||
{ url: req.url, method: req.method, endpoint: 'sharedInbox' },
|
{ url: req.url, method: req.method, endpoint: 'sharedInbox' },
|
||||||
'Failed to parse Activity'
|
|
||||||
);
|
|
||||||
return this.webServer.resourceNotFound(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activity.isValid()) {
|
|
||||||
this.log.warn(
|
|
||||||
{ activity, endpoint: 'sharedInbox' },
|
|
||||||
'Invalid or unsupported Activity'
|
'Invalid or unsupported Activity'
|
||||||
);
|
);
|
||||||
return this.webServer.badRequest(resp);
|
return activity
|
||||||
|
? this.webServer.badRequest(resp)
|
||||||
|
: this.webServer.notImplemented(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (activity.type) {
|
switch (activity.type) {
|
||||||
|
@ -287,8 +275,11 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
{ type: activity.type },
|
{ type: activity.type },
|
||||||
'Invalid or unknown Activity type'
|
'Invalid or unknown Activity type'
|
||||||
);
|
);
|
||||||
return this.webServer.resourceNotFound(resp);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't understand the 'type'
|
||||||
|
return this.webServer.notImplemented(resp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,28 +300,28 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
inboxUpdateObject(inboxType, req, resp, activity) {
|
inboxUpdateObject(inboxType, req, resp, activity) {
|
||||||
const objectIdToUpdate = _.get(activity, 'object.id');
|
const updateObjectId = _.get(activity, 'object.id');
|
||||||
const objectType = _.get(activity, 'object.type');
|
const objectType = _.get(activity, 'object.type');
|
||||||
|
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{ inboxType, objectId: objectIdToUpdate, type: objectType },
|
{ inboxType, objectId: updateObjectId, type: objectType },
|
||||||
'Inbox Object "Update" request'
|
'Inbox Object "Update" request'
|
||||||
);
|
);
|
||||||
|
|
||||||
// :TODO: other types...
|
// :TODO: other types...
|
||||||
if (!objectIdToUpdate || !['Note'].includes(objectType)) {
|
if (!updateObjectId || !['Note'].includes(objectType)) {
|
||||||
return this.webServer.resourceNotFound(resp);
|
return this.webServer.notImplemented(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note's are wrapped in Create Activities
|
// Note's are wrapped in Create Activities
|
||||||
Collection.objectByEmbeddedId(objectIdToUpdate, (err, obj) => {
|
Collection.objectByEmbeddedId(updateObjectId, (err, obj) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return this.webServer.internalServerError(resp, err);
|
return this.webServer.internalServerError(resp, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
// no match
|
// no match, but respond as accepted and hopefully they don't ask again
|
||||||
return this.webServer.resourceNotFound(resp);
|
return this.webServer.accepted(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OK, the object exists; Does the caller have permission
|
// OK, the object exists; Does the caller have permission
|
||||||
|
@ -346,7 +337,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
if (updateTargetUrl.host !== updaterUrl.host) {
|
if (updateTargetUrl.host !== updaterUrl.host) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{
|
{
|
||||||
objectId: objectIdToUpdate,
|
objectId: updateObjectId,
|
||||||
type: objectType,
|
type: objectType,
|
||||||
updateTargetHost: updateTargetUrl.host,
|
updateTargetHost: updateTargetUrl.host,
|
||||||
requestorHost: updaterUrl.host,
|
requestorHost: updaterUrl.host,
|
||||||
|
@ -358,7 +349,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
Collection.updateCollectionEntry(
|
Collection.updateCollectionEntry(
|
||||||
'inbox',
|
'inbox',
|
||||||
objectIdToUpdate,
|
updateObjectId,
|
||||||
activity,
|
activity,
|
||||||
err => {
|
err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -367,7 +358,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{
|
{
|
||||||
objectId: objectIdToUpdate,
|
objectId: updateObjectId,
|
||||||
type: objectType,
|
type: objectType,
|
||||||
collection: 'inbox',
|
collection: 'inbox',
|
||||||
},
|
},
|
||||||
|
@ -444,7 +435,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return this._storeNoteAsMessage(
|
return this._storeNoteAsMessage(
|
||||||
activity.id,
|
activity.id,
|
||||||
'All',
|
'All',
|
||||||
Message.WellKnownAreaTags.ActivityPubSharedInbox,
|
Message.WellKnownAreaTags.ActivityPubShared,
|
||||||
note,
|
note,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
|
@ -527,13 +518,9 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify(collection);
|
const body = JSON.stringify(collection);
|
||||||
const headers = {
|
return this.webServer.ok(resp, body, {
|
||||||
'Content-Type': ActivityStreamMediaType,
|
'Content-Type': ActivityStreamMediaType,
|
||||||
'Content-Length': Buffer(body).length,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
return resp.end(body);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,8 +555,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
// :TODO: support a template here
|
// :TODO: support a template here
|
||||||
|
|
||||||
resp.writeHead(200, { 'Content-Type': 'text/html' });
|
return this.webServer.ok(resp, note.content);
|
||||||
return resp.end(note.content);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,11 +605,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
'Follow request'
|
'Follow request'
|
||||||
);
|
);
|
||||||
|
|
||||||
const ok = () => {
|
|
||||||
resp.writeHead(200, { 'Content-Type': 'text/html' });
|
|
||||||
return resp.end('');
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the user blindly accepts Followers, we can persist
|
// If the user blindly accepts Followers, we can persist
|
||||||
// and send an 'Accept' now. Otherwise, we need to queue this
|
// and send an 'Accept' now. Otherwise, we need to queue this
|
||||||
|
@ -633,7 +614,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
const activityPubSettings = ActivityPubSettings.fromUser(localUser);
|
const activityPubSettings = ActivityPubSettings.fromUser(localUser);
|
||||||
if (!activityPubSettings.manuallyApproveFollowers) {
|
if (!activityPubSettings.manuallyApproveFollowers) {
|
||||||
this._recordAcceptedFollowRequest(localUser, remoteActor, activity);
|
this._recordAcceptedFollowRequest(localUser, remoteActor, activity);
|
||||||
return ok();
|
return this.webServer.ok(resp);
|
||||||
} else {
|
} else {
|
||||||
Collection.addFollowRequest(
|
Collection.addFollowRequest(
|
||||||
localUser,
|
localUser,
|
||||||
|
@ -645,7 +626,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return this.internalServerError(resp, err);
|
return this.internalServerError(resp, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok();
|
return this.webServer.ok(resp);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -836,13 +817,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
const body = JSON.stringify(localActor);
|
const body = JSON.stringify(localActor);
|
||||||
|
|
||||||
const headers = {
|
return this.webServer.ok(resp, body, { 'Content-Type': ActivityStreamMediaType });
|
||||||
'Content-Type': ActivityStreamMediaType,
|
|
||||||
'Content-Length': Buffer(body).length,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
return resp.end(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_standardSelfHandler(localUser, localActor, req, resp) {
|
_standardSelfHandler(localUser, localActor, req, resp) {
|
||||||
|
@ -872,13 +847,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
`Serving ActivityPub Profile for "${localUser.username}"`
|
`Serving ActivityPub Profile for "${localUser.username}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
const headers = {
|
return this.webServer.ok(resp, body, { 'Content-Type': contentType });
|
||||||
'Content-Type': contentType,
|
|
||||||
'Content-Length': Buffer(body).length,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
return resp.end(body);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue