Files
indiekit-endpoint-activitypub/lib/actor.js
Ricardo da625592fd feat: ActivityPub federation endpoint for Indiekit
Implements full ActivityPub federation as an Indiekit plugin:
- Actor document (Person) with RSA key pair for HTTP Signatures
- WebFinger discovery (acct:rick@rmendes.net)
- Inbox: handles Follow, Undo, Like, Announce, Create, Delete, Move
- Outbox: serves published posts as ActivityStreams 2.0
- Content negotiation: AS2 JSON for AP clients, passthrough for browsers
- JF2-to-AS2 converter for all Indiekit post types
- Syndicator integration (pre-ticked checkbox for delivery to followers)
- Mastodon migration: alias config, CSV import for followers/following
- Admin UI: dashboard, followers, following, activity log, migration page
- Data retention: configurable TTL on activities, optional raw JSON storage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:13:51 +01:00

76 lines
2.1 KiB
JavaScript

/**
* Build an ActivityPub Person actor document.
*
* This is the identity document that remote servers fetch to learn about
* this actor — it contains the profile, endpoints, and the public key
* used to verify HTTP Signatures on outbound activities.
*
* @param {object} options
* @param {string} options.actorUrl - Actor URL (also the Person id)
* @param {string} options.publicationUrl - Publication base URL (trailing slash)
* @param {string} options.mountPath - Plugin mount path (e.g. "/activitypub")
* @param {string} options.handle - Preferred username (e.g. "rick")
* @param {string} options.name - Display name
* @param {string} options.summary - Bio / profile summary
* @param {string} options.icon - Avatar URL or path
* @param {string} options.alsoKnownAs - Previous account URL (for Mastodon migration)
* @param {string} options.publicKeyPem - PEM-encoded RSA public key
* @returns {object} ActivityStreams Person document
*/
export function buildActorDocument(options) {
const {
actorUrl,
publicationUrl,
mountPath,
handle,
name,
summary,
icon,
alsoKnownAs,
publicKeyPem,
} = options;
const baseUrl = publicationUrl.replace(/\/$/, "");
const actor = {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
type: "Person",
id: actorUrl,
preferredUsername: handle,
name: name || handle,
url: actorUrl,
inbox: `${baseUrl}${mountPath}/inbox`,
outbox: `${baseUrl}${mountPath}/outbox`,
followers: `${baseUrl}${mountPath}/followers`,
following: `${baseUrl}${mountPath}/following`,
publicKey: {
id: `${actorUrl}#main-key`,
owner: actorUrl,
publicKeyPem,
},
};
if (summary) {
actor.summary = summary;
}
if (icon) {
const iconUrl = icon.startsWith("http") ? icon : `${baseUrl}${icon.startsWith("/") ? "" : "/"}${icon}`;
actor.icon = {
type: "Image",
url: iconUrl,
};
}
if (alsoKnownAs) {
actor.alsoKnownAs = Array.isArray(alsoKnownAs)
? alsoKnownAs
: [alsoKnownAs];
}
return actor;
}