Hello everyone,
Following last week’s Solid Practitioners meeting where I’ve presented ActivityPods and seen (perhaps for the first time) a lot of enthusiasm regarding the idea of integrating ActivityPub as a communication protocol within Solid, and at the request of several attendees, I’ve started to compile a list of what would be needed to add an ActivityPub layer to Solid Pods.
But first a little intro to give some context…
What is ActivityPub ?
ActivityPub is a simple communication protocol that allows any actor on the web to communicate with any other actor, no matter what software or server they are using. Actors can be persons, groups, organizations or softwares (bots).
Every actor can publish activities through its own outbox. The ActivityPub server then forwards these activities to the recipients’ inboxes (using HTTP signature for authentication). Inboxes and outboxes can also be fetched to see the activities that have been posted (if permissions allow).
In a way, ActivityPub is like email, but thanks to the use of RDF, it can describe any kind of social activity. Many commonly-used social activities are described in the ActivityStreams (AS) ontology, but it can be extended as much as needed with other ontologies.
Why ActivityPub is needed by Solid ?
The other day, @nikoNG from NextGraph shared with us the three types of communication protocols that can be abstracted (and that he also implemented in NextGraph): PubSub (Streaming), RPC (Remote Procedure Call) and Inbox. Here is the slide I showed during last week’s meeting:
As we can see in the violet area, Solid already use all three types of communication protocols, but in our opinion the Linked Data Notification (LDN) is too lightweight to fully address the related needs (ActivityPub was built upon LDN, which is why it uses the same predicate for the inbox)
The real need is that every WebID user has a clear way to be reached, and that it is also able to respond if needed. Once WebIDs can communicate with each others, many other things become possible and, maybe, we can rely less on the RPC-type of communication that is common in Solid specs.
Regarding Solid Notifications, it is a good protocol, but it requires the client to subscribe first, otherwise they don’t get any notifications. So it is good to watch resources (and we use it in ActivityPods to watch for inbox/outbox activities) but it is too limited for open communication between actors, since an actor cannot listen to the all other actors on the web.
What MUST be implemented ?
Here’s what must be implemented to have a very basic ActivityPub communication between actors:
- Generate a private/public keypair on the Pod and attach the public key to the WebID
- Attach a
as:outbox
endpoint to the WebID (ref.)- POST allow the actor (WebID owner) to post activities, which are then forwarded to recipients’ inboxes with a HTTP signature authentication
- GET return an OrderedCollection with a dereferenced list of activities that the authenticated user has the right to see
- Attach a
as:inbox
endpoint to the WebID (ref.)- POST allow other servers to send activities to the actor, with a HTTP signature authentication.
- GET return an OrderedCollection with a dereferenced list of activities that the authenticated user has the right to see
- Handle side effects
- Persist activities sent through the outbox somewhere where they can be fetched (reachable URI)
- Give
acl:Read
permission to the recipients (if theas:Public
addressing is used, give anonymous read permission)
What SHOULD be implemented ?
The following is not required by the specs, but if it is not implemented, users probably won’t be able to communicate with Mastodon or most other ActivityPub softwares.
- Handle the Webfinger endpoint at the root of the WebID server (
/.well-known/webfinger
) - Attach a
as:Person
(oras:Organization
oras:Group
) type to WebID - Attach AS-specific predicates such as
as:name
oras:preferredUsername
to WebID - Attach followers/following AS collections to the WebID
- Handle side effects
- When receiving or emitting Follow activities, add the corresponding items to the followers/following AS collections. (ref.)
- Persist objects that are created with a
as:Create
activity (ref.) - Update objects that are updated with a
as:Update
activity (ref.) - Replace objects that are deleted with a
as:Delete
with aas:Tombstone
(ref.)
- All JSON-LD objects should use the
https://www.w3.org/ns/activitystreams
context or they may not be understood by other Mastodon servers (see below for more details) - ActivityStreams
Content-Type
headers should beapplication/ld+json; profile="https://www.w3.org/ns/activitystreams"
orapplication/activity+json
. At the moment, Mastodon servers don’t work without these headers (see below for more details)
What MAY be implemented ?
If we want to implement the whole ActivityPub spec, here are other things to consider
- Attach
liked
AS collections to the WebID - Attach
likes
/shared
/replies
AS collections to resources (for ActivityPods, we create them only when related activities are sent) - Handle side effects
- When receiving or emitting
Like
activities, add the corresponding items to theliked/likes
AS collections. - When receiving objects with a
as:inReplyTo
predicate, add the object to theresplies
AS collection attached to the object.
- When receiving or emitting
How could this be implemented ?
I believe ActivityPub could be added to Solid in several ways:
- As an extension of CSS, that is activated by default
- As an extension of CSS, that can be activated by the server owner
- As an external agent, similar to SAI’s Authorization Agent
The advantage of the last option is that it could be installed upon any Solid server, and so the user would not depend on the choice of the server owner. In fact, if some Solid users have ActivityPub inboxes, and others don’t, it will certainly be difficult to have working apps.
But I see several difficulties that are highlighted below (there may be others).
Difficulty #1: Inbox and outbox endpoints
In the scenario of an ActivityPub agent, the inbox and outbox endpoints would necessarily need to be on the server of this agent, because (as we’ve seen above) the POST endpoint is very specific and the GET must return an AS OrderedCollection, which is IMO impossible to handle as a regular Solid resource (more about this below).
This may not be a problem. But the inbox and outbox are important aspects of ActivityPub so they must be accessible by any user and also applications registered through SAI. It must be possible to fetch or listen to them, if we have the authorizations. So I wonder how well two Solid agents (SAI Authorization Agent + the ActivityPub agent) could work together, especially on endpoints which are outside of the user’s Pod ? This would require more discussions and thinking.
Another thing to consider is that it would mean the content of the inbox and outbox (ie. the URIs of related activities) would be stored outside of the Pod, in the ActivityPub agent server. I’m not sure if that’s such a good practice. If another ActivityPub agent is created, and the Pod owner wants to switch to this agent, they will lose their inbox and outbox content.
Difficulty #2: Webfinger
The /.well-known/webfinger
endpoint must be at the root of the identity provider, so it cannot be handled by an ActivityPub agent. I know that @thhck developed a CSS extension for that, so it would require to activate it by default (it’s a very lightweight spec).
Difficulty #3: ActivityStreams collections
AS collections are a big subject so I will only touch upon it.
Some people suggested that LDP containers could be displayed as AS collection if the Accept
header was different. However, from a semantic perspective, a ldp:Container
with resources linked through ldp:includes
is not the same as a as:Collection
with resources linked through as:items
. Overall it’s not a good practice to change the content of an RDF resource based on the Accept
header.
Another solution would be to use LDP Direct Containers to double ldp:includes
links with as:items
. But Solid only allow LDP Basic Containers.
Anyway, all that would not solve the following problems:
- Collections handle pagination in a very different way from LDP containers (where pagination is optional, passed through a header). If collections were simple resources, we would need to create possibly one resource per page.
- Collections can be ordered, and in this case we are supposed to persist this order. The LDP paging spec has something about ordering, but it is done “on the fly”. Order persistance is overall difficult to handle with RDF data (we are having a long discussions on this issue about this subject)
- Some AS collections dereference the contained items, and other don’t. The specs are not very clear about this. Inboxes and outboxes are supposed to dereference activities they contain. But they must only dereference activities that the authenticated agent has the right to see. Nothing like this exists in LDP containers.
As a conclusion, with the current Solid implementations, we can display non-ordered non-paginated collections as simple LDP resources. But as soon as we want to paginate, sort or dereference them, it becomes more difficult. Luckily only the inbox and outbox really need to be paginated and ordered, so it can be handled on an external endpoint.
Difficulty #4: JSON-LD context
Most ActivityPub-compatible servers expect JSON-LD resources with a https://www.w3.org/ns/activitystreams
context. This context transforms @type
to type
, @id
to id
and removes the as:
prefix. Currently there is nothing in Solid that defines how contexts should be used, since they are not saved.
For POSTing data between outboxes and inboxes, we can prepare a JSON-LD based on this context without problem. But most ActivityPub servers expect to GET objects and activities also with this context.
In ActivityPods, we defined a new JsonLdContext
header to force JSON-LD data to be formatted with a given context, but this is not standard.
Difficulty #5: Content-Type headers
Since a recent version, Mastodon servers expect that the actors, activities and objects it GETs return a Content-Type
header application/ld+json; profile="https://www.w3.org/ns/activitystreams"
or application/activity+json
.
I don’t know why they made this change. Maybe with some discussions, they could remove this requirement, which is not part of the ActivityPub specs.
Where to go next ?
This is just a brief overview of the various challenges that would be involved in making Solid fully compatible with ActivityPub.
I know an attempt was made during the “Solid for Social Networks” hackathon last year, but I can’t remember exactly how it turned out, and the blog post about it now redirects to another blog post. Can someone help about this? (Ping @hzbarcea)
If creating an ActivityPub agent proves to be too difficult, we should probably consider developing a CSS extension instead. I know that @thhck might be interested in working on this, if there was funding available.
Personally I won’t have time to develop either an ActivityPub agent or a CSS extension, but I can spend some time sharing my understanding of ActivityPub (I’ve been building ActivityPub servers since the spec came out in 2018, so I’m pretty fluent in that “language”…). I can also help writing a specification for the “glue” needed to make Solid Pods compatible with ActivityPub. This would be very useful for ActivityPods too!
So if we want to go further into this, we should probably consider forming a Community Group on the subject. Who would be interested in taking an active part to such a group ? If there is enough interest, we could have a first meeting in January?
Let me know !
Sébastien