Skip to content

Commit

Permalink
Supported delivering Activities to a Collection of Actors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
allouis committed May 16, 2024
1 parent 6038916 commit d15858e
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 4 deletions.
66 changes: 65 additions & 1 deletion ghost/ghost/src/core/activitypub/tell-the-world.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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');
});
});
});
29 changes: 27 additions & 2 deletions ghost/ghost/src/core/activitypub/tell-the-world.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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);
}
}
}
}
}
}
}

Expand All @@ -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[]>{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion ghost/ghost/src/listeners/activity.listener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down

0 comments on commit d15858e

Please sign in to comment.