0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Supported delivering Activities to a Collection of Actors

ref https://linear.app/tryghost/issue/MOM-126

Now that we're setting the recipient of our Create Activites to the Followers
Collection, we need to actually dereference it and pull out all the inboxes.
This is all done over the network at the moment, but we'll start storing this
information locally when we've got the DB wired up.
This commit is contained in:
Fabien O'Carroll 2024-05-16 11:46:59 +07:00 committed by Fabien 'egg' O'Carroll
parent 603891645d
commit d15858e16a
4 changed files with 97 additions and 4 deletions

View file

@ -14,7 +14,7 @@ describe('TheWorld', function () {
nock.enableNetConnect();
});
it('Can deliver the activity to the inbox of the desired recipient', async function () {
const service = new TheWorld(new URL('https://base.com'));
const service = new TheWorld(new URL('https://base.com'), console);
const actor = Actor.create({
username: 'Testing'
@ -47,5 +47,69 @@ describe('TheWorld', function () {
assert(actorFetch.isDone(), 'Expected actor to be fetched');
assert(activityDelivery.isDone(), 'Expected activity to be delivered');
});
it('Can deliver the activity to the inboxes of a collection of actors', async function () {
const service = new TheWorld(new URL('https://base.com'), console);
const actor = Actor.create({
username: 'Testing'
});
const followers = new URI('https://main.ghost.org/activitypub/followers/deadbeefdeadbeefdeadbeef');
const activity = new Activity({
type: 'Create',
activity: null,
actor: actor.actorId,
object: {
id: new URI('https://main.ghost.org/hello-world'),
type: 'Note',
content: '<p> Hello, world. </p>'
},
to: followers
});
nock('https://main.ghost.org')
.get('/activitypub/followers/deadbeefdeadbeefdeadbeef')
.reply(200, {
'@context': '',
type: 'Collection',
totalItems: 3,
items: [
'https://main.ghost.org/activitypub/actor/deadbeefdeadbeefdeadbeef',
{
id: 'https://main.ghost.org/activitypub/actor/beefdeadbeefdeadbeefdead'
},
{
invalid: true
}
]
});
nock('https://main.ghost.org')
.get('/activitypub/actor/deadbeefdeadbeefdeadbeef')
.reply(200, {
inbox: 'https://main.ghost.org/activitypub/inbox/deadbeefdeadbeefdeadbeef'
});
nock('https://main.ghost.org')
.get('/activitypub/actor/beefdeadbeefdeadbeefdead')
.reply(200, {
inbox: 'https://main.ghost.org/activitypub/inbox/beefdeadbeefdeadbeefdead'
});
const firstActivityDelivery = nock('https://main.ghost.org')
.post('/activitypub/inbox/deadbeefdeadbeefdeadbeef')
.reply(201, {});
const secondActivityDelivery = nock('https://main.ghost.org')
.post('/activitypub/inbox/beefdeadbeefdeadbeefdead')
.reply(201, {});
await service.deliverActivity(activity, actor);
assert(firstActivityDelivery.isDone(), 'Expected activity to be delivered');
assert(secondActivityDelivery.isDone(), 'Expected activity to be delivered');
});
});
});

View file

@ -4,7 +4,8 @@ import {Actor} from './actor.entity';
export class TheWorld {
constructor(
@Inject('ActivityPubBaseURL') private readonly url: URL
@Inject('ActivityPubBaseURL') private readonly url: URL,
@Inject('logger') private readonly logger: Console
) {}
async deliverActivity(activity: Activity, actor: Actor): Promise<void> {
@ -15,6 +16,26 @@ export class TheWorld {
const inbox = new URL(data.inbox);
await this.sendActivity(inbox, activity, actor);
}
if ('type' in data && data.type === 'Collection') {
if ('items' in data && Array.isArray(data.items)) {
for (const item of data.items) {
let url;
if (typeof item === 'string') {
url = new URL(item);
} else if ('id' in item && typeof item.id === 'string') {
url = new URL(item.id);
}
if (url) {
const fetchedActor = await this.fetchForActor(url.href, actor);
if ('inbox' in fetchedActor && typeof fetchedActor.inbox === 'string') {
const inbox = new URL(fetchedActor.inbox);
await this.sendActivity(inbox, activity, actor);
}
}
}
}
}
}
}
@ -27,7 +48,11 @@ export class TheWorld {
body: JSON.stringify(activity.getJSONLD(this.url))
});
const signedRequest = await from.sign(request, this.url);
await fetch(signedRequest);
try {
await fetch(signedRequest);
} catch (err) {
this.logger.error(err);
}
}
private async getRecipients(activity: Activity): Promise<URL[]>{

View file

@ -21,6 +21,10 @@ describe('ActivityPubController', function () {
const moduleRef = await Test.createTestingModule({
controllers: [ActivityPubController],
providers: [
{
provide: 'logger',
useValue: console
},
{
provide: 'ActivityPubBaseURL',
useValue: new URL('https://example.com')

View file

@ -17,7 +17,7 @@ describe('ActivityListener', function () {
calledWith.push([activity, actor]);
}
}
const listener = new ActivityListener(new MockTheWorld(new URL('https://example.com')));
const listener = new ActivityListener(new MockTheWorld(new URL('https://example.com'), console));
const actor = Actor.create({
username: 'Testing'