This document describes an application of Server-Sent Events to the ActivityPub API.
This is an experimental specification and is undergoing regular revisions.
The [[[!ActivityPub]]] API is a RESTful interface for social networking
servers. It provides a readable interface for locally-hosted objects via
HTTP GET to their id
property URL, and for remotely-hosted
objects via the proxyUrl
endpoint. A writeable interface is
provided with an HTTP POST to the user's
outbox
collection.
To get updated versions of an object or collection, the client application has to repeatedly fetch the object -- "polling" for updates at a given interval. This has three disadvantages:
One alternative to the polling model is server push. In this architecture, the client establishes a long-running connection to the server. The server then sends update notifications to the client, as close to the actual modification time as possible, so that the client can modify its local representation of the object.
Several server push technologies exist in the Web ecosystem. This
document describes an application of [[[!eventsource]]] to the
ActivityPub API. Server-Sent Events have the advantage of being
well-supported in many browser environments, and easy to support in
other contexts. Their downside, compared to some other technologies such
as [[[websockets]]], is that they are one-way and read-only; the server
can send updates to the client, but the client cannot use the same
channel to send updates to the server. The POST interface through the
user's outbox
should suffice for most applications.
This specification uses a constrained subset of the [[[!activitystreams-vocabulary]]] to represent changes to ActivityPub objects. This has the advantage of not requiring a separate vocabulary or format for changes, which are already well represented in AS2. On the downside, it blurs the line between the data to be represented, and the changes to that data.
The following user stories illustrate how this specification may be used:
The publisher of an ActivityPub object can declare an endpoint to retrieve an event stream for events related to that object with the eventStream property.
Events in the event stream SHOULD have either the object, or one
of its object properties, as the object
or
target
of the event.
The client application MAY use the Authorization
header to
pass an [[[rfc6749]]] access token to the server.
Because the EventSource
interface does not enable passing
custom headers, the client application MAY pass the access token as an
URL parameter access_token
.
Events from the server are in the form of [[[!activitystreams-core]]] Activity objects, in [[[!json-ld]]] format.
Activity objects MAY include an actor
property, and MAY
include an id
property.
An Add
event is sent when a new item is added to a
collection.
A Remove
event is sent when an existing item is removed
from a collection.
An Update
event is sent when an object has been changed.
The object
property of the event should contain a full
representation of the changed object.
The Delete
activity is sent when an object has been
deleted.
The client may want to be notified of changes to remote objects; that
is, objects hosted on remote servers. Similar to the
proxyUrl
endpoint, a publisher can share a
proxyEventStream
property in the
endpoints
property of an actor to indicate a local proxy
URL that can be used to be notified of events about remote objects.
The proxy event stream URL takes two arguments (in addition to any arguments defined in the URL):
id
: The id of the object to get events foraccess_token
: An OAuth 2.0 access token for
authentication
Including the access_token
in the URL for the
EventSource
can leak the access token; the token will be
embedded when the URL is logged. Server software can mitigate this
issue by obscuring the access token in server logs, for example, by
replacing it with a string like "XXXXXXXX" or "********".
Real-time server updates are easier to process with a model-view-controller or similar architecture. The event handler, for server events, can look up models by id and call the appropriate methods to change the models and, consequently, change all the views that depend on that model.
This specification shifts complexity from the client to the server; responsibility for tracking which objects are related to which client is primarily on the side of the server.
One option for making this work is to keep a map of objects currently being observed with an event stream to the streams that are observing them. When a change is made to one of the objects, look up the relevant event streams, and send out an event on those streams. A reverse mapping (from streams to objects) can help with cleanup when the stream is torn down.
Server software that spans multiple processes will need to have a way to coordinate triggering event updates between processes. For example, if an activity is received from a remote server on a sharedInbox
endpoint, the effects of that change should propagate to open connections, even if they're being handled by another process.