Apolog: something between social networks and RSS feeds

Note: this is intended for limited sharing for now to be proof read, please don't post it (yet) on the orange or red sites ;)

RSS feeds were great, the people wants Facebook/Instagram/Twitter

RSS feeds where great because they were dead simple. Anybody who could host on the web could have an html blog and give it an RSS/Atom feed. But RSS sucks, because nobody wants to have a blog nowadays, at least not in the meaningful numbers which could help us all get away from the predatory companies running our usual social networks.

Social networks were great, it was so easy to share with friends updates about your life, pictures, video, and follow your favorite artist, venue, tv show, coffee shop... But one day, the mega-corps running them remembered that they have a moral obligation to sacrifice decency, principles, and the well being of their users to turn into profit for their shareholders.

Modern attempts to run federated networks leave me skeptical: ActivityPub is a complex protocol, and I have yet to wrap my mind around ATProto. They also suffer from a massive network effect, and explaining them to neophytes is hard. Plus they never manage to get fully decentralized: activity pub is supposed to be like email, with clients and servers, but everybody ends up using somethings providing both the server and client, as with web-mails.

Apolog: overview

Apolog does less than Facebook/Instagram/Mastodon

Apolog is an idea I have had, that sits somewhere in this space. It is a pretty simple protocol, that seems to offer ways to be implemented very easily, but also in more complex and efficient ways. I'm not some revolutionary genius, the reason it does so is because it forgoes lots of what is now part of a social networks, and first of all the reactions and interactions. No DM, no comments, no like. Just feeds of content from your friends.

Separating concerns: the three servers

Self/community hosting is a hard sell in part because it can be hard for people to trust its longevity. To alleviate this issue, the protocol distinguishes three kinds of servers (yes, I know I said it was simple, bear with me): the identity server, the feed server, and the content server. These three logical servers can be implemented by the same machine, or even the same binary, but they could also not be. The protocol lends itself to dead simple single user hosting, or more complex hosting for multiple users.

Having three different servers allows to more easily separate concerns: the only one that should be long lasting is the identity server: loosing access to it will mean that your friends wont find your feeds and you won't be able to have access to the feeds with private access you had been granted. The feed server is the one serving actual feeds of content, it is the one with potentially the more complex backend logic (but can also just be static files) and caching behavior for high performance setup. Last, the content server only job is serving static files. An high-performance setup will see it maximizes throughput, latency, and storage space.

Consuming content: the client

Servers would have little purpose without clients. Much like e-mail or RSS, apolog feeds are meant to be consumed by the user from a single place: you're not hopping from blog to blog, but rather have all your content centralized in one interface. Contrary to RSS, this interface is meant to be able to (nicely) display videos, images, audio, etc... ie the content making most of today's internet user generated objects.

An example interface is provided here, it is only an example of what it could look like (and I definitely asked gemini to generate it), and some buttons just don't do anything when clicked, but you (hopefully) get the idea.

The gory details

The protocol is a bunch of JSON over HTTP. In a way, it is asking: what if RSS was instead a REST API?

The identity server

The identity of a user is a URL, pointing to a directory. In this directory, there should be a json file called id.json, with content like

{
    "display-name": "Toto L'asticot",
    "avatar": "https://picsum.photos/id/302/200/200.jpg",
    // A Json web key set. May not be unique to the user!
    "public-key": { }, 
    "feeds": {
        "for-friendly-eyes": {
            "url": "https://feeds.example.com/toto/friends/",
            "private": true
        },
        "public": {
            "url": "https://feeds.example.com/toto/public/",
            "private": false
        },
    }
}

The display name and avatar are pretty obvious. The feeds is a map of where each of this user's feed can be found. The public key part is interesting: these public keys are not necessary controlled by the user! Instead, the corresponding private keys are the one of the "identity authority": possessing them proves that one has authority to authenticate the user at this url. On a simple setup for techies, the end user may be in possession of the private key, in more complex hosted settings the server can be, and will deliver JWT to the user showing that they are indeed who they claim.

The feed server

The feed is a set of events (or "posts" if you prefer), each encoded as JSON.

An event is of the form:

{
    "event-id": 17364,
    "tick": 34567, 
    "created-at": "2025-01-01T05:34",
    "last-modified-at": "2025-01-01T05:34",
    "title": "Great mountain!",
    "description": "Carol and I summitted Mount Wycheproof yesterday!",
    "content": [
        {
            "urls": [
                "https://picsum.photos/id/301/800/200.jpg",
                "https://example.com/same_picture/but_in_another_format.webm"
            ], 
            "alt": "Us at the beginning of the hike"
        },
        {
            "urls": [
                "https://picsum.photos/id/30/800/200.jpg",
                "https://example.com/same_picture/but_in_another_format.webm"
            ], 
            "alt": "Us at the end of the hike"
        },
        "An inline text. Texts can be inline, instead of an a remote server I guess?"
    ],
    "tags": [ "hike", "friends"]
}

The feed server offers one endpoint: /events. It has one optional parameter: selectOn and a following optional option since. selectOn can be one of event-id or tick

Response invariants

  1. The response is guaranteed to be a "contiguous block" of events based on the property indicated by selectOn. This means if events with property values X and Y are in the response, any existing event with a property value between X and Y will also be included.
  2. The since parameter acts as a lower bound, if it is missing, this bound is considered to be "minus infinite". The list will always contain the one event with the smallest property value that is greater than since, if it exists.
  3. The complete flag indicates if the returned list contains all the remaining events in the collection past the since value. If false, the client should make another request using the last event's property value as the new since parameter to get the next page.

The returned json will look like:

{
    "complete": false,
    "events": [
        // Some events as illustrated above
    ]
}

Remarks

Notice that because the returned values are only loosely constrained, it is perfectly ok to alway return the same JSON containing all the events, making for a very easy implementation that matches pretty closely RSS. On the other hand, because the pagination size is not defined, it allows efficient caching by letting complex implementation "align back" to their chosen pagination step in the first response, ensuring the remaining ones are in cache.

The content is not very descriptive by design: HTTP HEAD requests can be used to identify its type, size etc... An high performance HTTP 2 or 3 implementation may proactively push these headers if the feed and object servers are the same.

It would be possible to add more metadata in the JSON in case we want to really keep the feed and file server separated.

We could add an optional strict field to the response indicating if only events matching the constraints are returned.

The content server

The content server is simply an HTTP server for static files. Nginx goes brrrrr. Well, except if the feed is private (see below).

The client

The client acts much like an RSS reader, polling each followed feed in turn.

Private feeds

To restrict feeds (those marked "private": true in id.json) to some users, HTTP authentication is used. To successfully authenticate, the client must either prove that it owns one of the private keys listed in id.json or that it has been authenticated by such an owner, typically through a Json Web Token.

Remarks

The mechanism to add/remove authorized readers of the feed remain to be defined, but having a way to HTTP POST for example a "follow request" is knowingly excluded, because it would be hard to control spam, and spam control leads tends to centralization (you would need to request to follow from a "reputable" server).

The mechanism by which the client get the JWT from the identity server is not specified for now, but OAuth could be a possibility for complex multi user setups.

Conclusion

This if very much in its infancy, but if you're interested or have suggestions, let me know!