paint-brush
A Brief Introduction of ActivityPub: The Future of Social Networksby@thebojda
9,052 reads
9,052 reads

A Brief Introduction of ActivityPub: The Future of Social Networks

by Laszlo FazekasOctober 7th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

ActivityPub is an open, distributed social network protocol standardized by the World Wide Web. Its only weakness is that it uses the HTTP protocol, which requires a central server. ActivityPub protocol should be extended with support for already existing decentralized protocols, which would allow the Fediverse to develop towards an even more open and decentralized direction.

People Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - A Brief Introduction of ActivityPub: The Future of Social Networks
Laszlo Fazekas HackerNoon profile picture

ActivityPub is an open, distributed social network protocol standardized by the World Wide Web Consortium. Its first version was released in 2018, so it is not new. It is used by several social networks, including Mastodon, which has recently seen a significant increase in popularity when Elon Musk bought Twitter and made changes that did not please many.


In addition, Tumblr and Facebook's alternative to Twitter, Threads, have also signaled that they will support the protocol in the future. Despite not being a new protocol, ActivityPub is now becoming increasingly popular.


As the European Union and the United States have long been concerned about the market monopoly of large social networks, I can imagine that in the future, under government pressure, the existing largest social networks such as Facebook and Twitter will also support the protocol and become part of the Fediverse. (The Fediverse is a universe of social networks consisting of providers supporting ActivityPub.)


I believe that in the future, we will not choose Twitter or Facebook because they will be closed and we will have no other choice.


The strength of each platform will come from who has better artificial intelligence to filter the incoming massive amount of data, to sift through, select, and summarize the most important information for the users.


Of course, to do this, one must know the user very well, which is also in the user's interest. In return, artificial intelligence can provide much more relevant advertisements.


In an open, accessible system, the competition will no longer be among social networks, but among artificial intelligence-based algorithms.


Let's see how ActivityPub works. The basic functionality is nicely summarized by this very simple diagram.

ActivityPub Explained


Source: https://activitypub.rocks/



The protocol has two main components: the inbox and the outbox. These are two API endpoints that can be accessed through the HTTP protocol. When a user wants to communicate something with the outside world (e.g., post some content), they send it to the outbox. Here, the system adds it to an appropriate list, which followers can access by reading the outbox.


If someone from the outside world wants to send something to the user (e.g., a non-public post that can only be seen by certain users), they send it to the inbox in encrypted form. The inbox is like an email inbox, where incoming content is collected and where the user can access it.


One can retrieve the location of the inbox and outbox using the WebFinger protocol. This can also be tested with the help of the ActivityPub Explorer, a super tool for experimenting with and understanding the protocol.


A username in ActivityPub is similar to an email address. For example, my Mastodon username is @[email protected]. This means that my user information is stored on the dm.me server under the name thebojda.


When entered into ActivityPub Explorer, it will query the following URL according to the WebFinger protocol to retrieve the data:


https://me.dm/.well-known/webfinger?resource=acct:thebojda%40me.dm


The response is a JSON object that looks like this:


{
  "subject":"acct:[email protected]",
  "aliases":[
    "https://me.dm/@thebojda",
    "https://me.dm/users/thebojda"
  ],
  "links":[
    {
      "rel":"http://webfinger.net/rel/profile-page",
      "type":"text/html",
      "href":"https://me.dm/@thebojda"
    },
    {
      "rel":"self",
      "type":"application/activity+json",
      "href":"https://me.dm/users/thebojda"
    },
    {
      "rel":"http://ostatus.org/schema/1.0/subscribe",
      "template":"https://me.dm/authorize_interaction?uri={uri}"
    }
  ]
}


It can be determined from here that the ActivityPub information will be accessible at the https://me.dm/users/thebojda URL. If we enter this into the ActivityPub Explorer, it will return the following JSON:


{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
      "toot": "http://joinmastodon.org/ns#",
      "featured": {
        "@id": "toot:featured",
        "@type": "@id"
      },
      "featuredTags": {
        "@id": "toot:featuredTags",
        "@type": "@id"
      },
      "alsoKnownAs": {
        "@id": "as:alsoKnownAs",
        "@type": "@id"
      },
      "movedTo": {
        "@id": "as:movedTo",
        "@type": "@id"
      },
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "discoverable": "toot:discoverable",
      "Device": "toot:Device",
      "Ed25519Signature": "toot:Ed25519Signature",
      "Ed25519Key": "toot:Ed25519Key",
      "Curve25519Key": "toot:Curve25519Key",
      "EncryptedMessage": "toot:EncryptedMessage",
      "publicKeyBase64": "toot:publicKeyBase64",
      "deviceId": "toot:deviceId",
      "claim": {
        "@type": "@id",
        "@id": "toot:claim"
      },
      "fingerprintKey": {
        "@type": "@id",
        "@id": "toot:fingerprintKey"
      },
      "identityKey": {
        "@type": "@id",
        "@id": "toot:identityKey"
      },
      "devices": {
        "@type": "@id",
        "@id": "toot:devices"
      },
      "messageFranking": "toot:messageFranking",
      "messageType": "toot:messageType",
      "cipherText": "toot:cipherText",
      "suspended": "toot:suspended",
      "focalPoint": {
        "@container": "@list",
        "@id": "toot:focalPoint"
      }
    }
  ],
  "id": "https://me.dm/users/thebojda",
  "type": "Person",
  "following": "https://me.dm/users/thebojda/following",
  "followers": "https://me.dm/users/thebojda/followers",
  "inbox": "https://me.dm/users/thebojda/inbox",
  "outbox": "https://me.dm/users/thebojda/outbox",
  "featured": "https://me.dm/users/thebojda/collections/featured",
  "featuredTags": "https://me.dm/users/thebojda/collections/tags",
  "preferredUsername": "thebojda",
  "name": "Laszlo Fazekas",
  "summary": "<p>Software developer, contributing writer</p>",
  "url": "https://me.dm/@thebojda",
  "manuallyApprovesFollowers": false,
  "discoverable": false,
  "published": "2023-03-02T00:00:00Z",
  "devices": "https://me.dm/users/thebojda/collections/devices",
  "publicKey": {
    "id": "https://me.dm/users/thebojda#main-key",
    "owner": "https://me.dm/users/thebojda",
    "publicKeyPem": "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlvpgcOxBikiXfm9snZt EB3Y2BSnBW3s8nd1d4Z1wCzaNfa/woB6RDQJoQYEISbMpkEyWhDzI4jRDafpn5/j YiyyGka9U2KZHv1to0Ej9uVwUcQOnsq9iUtJGhOWCP27blKGTLKFEHtPL2Y4hp4Z kYzlh1x0aAyw8YC7/nbS8WDAeZNS7R3ET7Syhp3LKiCMmL1aCLSoOQJ5DdxVikMJ rJ9bzlUjxzCsm1aNBB0i269t4fD1evBO8QDhEAOnAZ6wLEV74j9SCjYMvKRV8z5i PQbhbKntXjn1XhbkIkj1D+yGRYYfnm1XyWTuaM4mDllvuOyqJ8GQVFADLc6KCQYz 7QIDAQAB -----END PUBLIC KEY----- "
  },
  "tag": [],
  "endpoints": {
    "sharedInbox": "https://me.dm/inbox"
  },
  "icon": {
    "type": "Image",
    "mediaType": "image/jpeg",
    "url": "https://media.me.dm/accounts/avatars/109/955/144/019/799/820/original/fe8e930e0f1467ac.jpeg"
  }
}


Here, you can find all the user-specific information about me, but what is most important, here are the URLs of the inbox and outbox endpoints, as well as the public key for encrypting messages sent to me. When querying the content of the outbox, the following JSON is returned:


{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://me.dm/users/thebojda/outbox",
  "type": "OrderedCollection",
  "totalItems": 1,
  "first": "https://me.dm/users/thebojda/outbox?page=true",
  "last": "https://me.dm/users/thebojda/outbox?min_id=0&page=true"
}


As the posts are divided into pages, here we can see the URLs of the first and last pages. By accessing the URL of the first page, we can view my entries, of which there is currently only one.


{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "ostatus": "http://ostatus.org#",
      "atomUri": "ostatus:atomUri",
      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
      "conversation": "ostatus:conversation",
      "sensitive": "as:sensitive",
      "toot": "http://joinmastodon.org/ns#",
      "votersCount": "toot:votersCount"
    }
  ],
  "id": "https://me.dm/users/thebojda/outbox?page=true",
  "type": "OrderedCollectionPage",
  "prev": "https://me.dm/users/thebojda/outbox?min_id=109955178005562038&page=true",
  "partOf": "https://me.dm/users/thebojda/outbox",
  "orderedItems": [
    {
      "id": "https://me.dm/users/thebojda/statuses/109955178005562038/activity",
      "type": "Create",
      "actor": "https://me.dm/users/thebojda",
      "published": "2023-03-02T18:47:47Z",
      "to": [
        "https://www.w3.org/ns/activitystreams#Public"
      ],
      "cc": [
        "https://me.dm/users/thebojda/followers"
      ],
      "object": {
        "id": "https://me.dm/users/thebojda/statuses/109955178005562038",
        "type": "Note",
        "summary": null,
        "inReplyTo": null,
        "published": "2023-03-02T18:47:47Z",
        "url": "https://me.dm/@thebojda/109955178005562038",
        "attributedTo": "https://me.dm/users/thebojda",
        "to": [
          "https://www.w3.org/ns/activitystreams#Public"
        ],
        "cc": [
          "https://me.dm/users/thebojda/followers"
        ],
        "sensitive": false,
        "atomUri": "https://me.dm/users/thebojda/statuses/109955178005562038",
        "inReplyToAtomUri": null,
        "conversation": "tag:me.dm,2023-03-02:objectId=1406005:objectType=Conversation",
        "content": "<p>My &quot;ars poetica&quot;: How to Change the World?! Pocket Guide for People With a Messiah Complex <a href=\"https://medium.com/geekculture/how-to-change-the-world-pocket-guide-for-people-with-a-messiah-complex-dd2d16bb92a\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">medium.com/geekculture/how-to-</span><span class=\"invisible\">change-the-world-pocket-guide-for-people-with-a-messiah-complex-dd2d16bb92a</span></a></p>",
        "contentMap": {
          "en": "<p>My &quot;ars poetica&quot;: How to Change the World?! Pocket Guide for People With a Messiah Complex <a href=\"https://medium.com/geekculture/how-to-change-the-world-pocket-guide-for-people-with-a-messiah-complex-dd2d16bb92a\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">medium.com/geekculture/how-to-</span><span class=\"invisible\">change-the-world-pocket-guide-for-people-with-a-messiah-complex-dd2d16bb92a</span></a></p>"
        },
        "attachment": [],
        "tag": [],
        "replies": {
          "id": "https://me.dm/users/thebojda/statuses/109955178005562038/replies",
          "type": "Collection",
          "first": {
            "type": "CollectionPage",
            "next": "https://me.dm/users/thebojda/statuses/109955178005562038/replies?only_other_accounts=true&page=true",
            "partOf": "https://me.dm/users/thebojda/statuses/109955178005562038/replies",
            "items": []
          }
        }
      }
    }
  ]
}


Based on these examples, it is easy to implement a minimal client that can read the public posts of others or can publish public posts to the outside world. I will not go into the exact structure of the individual JSON files and the functioning of the inbox here.


Those can be found in the ActivityPub documentation.


As can be seen, ActivityPub is a relatively simple standard that is easy to understand and implement. Its only weakness is that it uses the HTTP protocol, which requires a central server. This gave rise to alternative protocols such as Nostr, which is Jack Dorsey's (the founder of Twitter) favorite project.


However, I believe there is no need for this, and it only causes further fragmentation of social networks. Instead, the ActivityPub protocol should be extended with support for already existing decentralized protocols, which would allow the Fediverse to develop towards an even more open and decentralized direction. In the following, I will demonstrate how this can be achieved.


One perfect solution for decentralizing descriptive data and feeds is to use Swarm Feeds or IPNS (I have a full article about these protocols). In the case of Ethereum Swarm, the feed identifier is a Swarm address, while in the case of IPNS, it is a public key.


Using these, an ActivityPub user would look like this: {swarm address}@swarm or {IPNS address}@IPNS. The user description could be queried from here.


For backward compatibility, gateway servers could also be used such as {swarm address}@gateway.ethswarm.org, which would return the user's description stored on Swarm through the Webfinger protocol, allowing existing systems (like Mastodon) to read this data without any modifications.


The implementation of the outbox would also be done through Swarm or IPNS feeds. In order to maintain backward compatibility, a "decentralized_outbox" field would need to be introduced, where a Swarm feed (bzz://...) or IPNS (ipns://...) address would be located.


In order to maintain backward compatibility, an additional gateway address could be included in the original "outbox" field so that existing clients can read these feeds without any changes.


Implementation of an inbox can be done in several ways. The Ethereum Swarm has a messaging system called PSS, but there are other alternatives as well, such as Waku. Here, the PSS or Waku address would be placed in a "decentralized_inbox" field, while the original inbox field would contain a gateway address.


With these few small extensions, ActivityPub can be made completely decentralized and "web3 compatible", without the need for introducing new protocols like Nostr.


According to Nostr's GitHub repo, the need for the new protocol and relay system is due to the potential censorship of users by ActivityPub servers and the lack of an incentive system. However, this problem is completely solved by the above extension.


Both IPNS and Swarm are censorship resistant, with Swarm also providing complete anonymity. Additionally, Swarm has its own incentive system, while FileCoin is available for IPNS.


I believe that the future clearly belongs to ActivityPub, and developers should focus on advancing and evolving ActivityPub instead of creating new protocols, in order to finally establish a comprehensive and truly unified Fediverse.