- cross-posted to:
- fediverse@lemmy.ml
43
- cross-posted to:
- fediverse@lemmy.ml
So, you're captivated by the fediverse—the decentralized social web powered by protocols like ActivityPub. Maybe you're dreaming of building the next great federated app, a unique space connected to Mastodon, Lemmy, Pixelfed, and more. The temptation to dive deep and implement ActivityPub yourself, from the ground up, is strong. Total control, right? Understanding every byte? Sounds cool!
But hold on a sec. Before you embark on that epic quest, let's talk reality. Implementing ActivityPub correctly isn't just one task; it's like juggling several complex standards while riding a unicycle… blindfolded. It’s hard.
That's where Fedify comes in. It's a TypeScript framework designed to handle the gnarliest parts of ActivityPub development, letting you focus on what makes your app special, not reinventing the federation wheel.
This post will break down the common headaches of DIY ActivityPub implementation and show how Fedify acts as the super-powered pain reliever, starting with the very foundation of how data is represented.Challenge #1: Data Modeling—Speaking ActivityStreams & JSON-LD Fluently
At its core, ActivityPub relies on the ActivityStreams 2.0 vocabulary to describe actions and objects, and it uses JSON-LD as the syntax to encode this vocabulary. While powerful, this combination introduces significant complexity right from the start.
First, understanding and correctly using the vast ActivityStreams vocabulary itself is a hurdle. You need to model everything—posts (Note, Article), profiles (Person, Organization), actions (Create, Follow, Like, Announce)—using the precise terms and properties defined in the specification. Manual JSON construction is tedious and prone to errors.
Second, JSON-LD, the encoding layer, has specific rules that make direct JSON manipulation surprisingly tricky:Missing vs. Empty Array: In JSON-LD, a property being absent is often semantically identical to it being present with an empty array. Your application logic needs to treat these cases equally when checking for values. For example, these two Note objects mean the same thing regarding the name property:// No name property{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "…"}// Equivalent to:{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "name": [], "content": "…"}Single Value vs. Array: Similarly, a property holding a single value directly is often equivalent to it holding a single-element array containing that value. Your code must anticipate both representations for the same meaning, like for the content property here:// Single value{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "Hello"}// Equivalent to:{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": ["Hello"]}Object Reference vs. Embedded Object: Properties can contain either the full JSON-LD object embedded directly or just a URI string referencing that object. Your application needs to be prepared to fetch the object's data if only a URI is given (a process called dereferencing). These two Announce activities are semantically equivalent (assuming the URIs resolve correctly):{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", // Embedded objects: "actor": { "type": "Person", "id": "http://sally.example.org/", "name": "Sally" }, "object": { "type": "Arrive", "id": "https://sally.example.com/arrive", /* ... */ }}// Equivalent to:{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", // URI references: "actor": "http://sally.example.org/", "object": "https://sally.example.com/arrive"} { /* ... */ });// Now GET /.well-known/webfinger?resource=acct:username@your.domain just works! { /* Handle follow */ }) .on(Undo, async (ctx, undo) => { /* Handle undo */ });// Define followers collection logicfederation.setFollowersDispatcher( "/users/{handle}/followers", async (ctx, handle, cursor) => { /* ... */ });.lhr.life/✔ Sent follow request to @@activitypub.academy.╭───────────────┬─────────────────────────────────────────╮│ Actor handle: │ i@.lhr.life │├───────────────┼─────────────────────────────────────────┤│ Actor URI: │ https://.lhr.life/i │├───────────────┼─────────────────────────────────────────┤│ Actor inbox: │ https://.lhr.life/i/inbox │├───────────────┼─────────────────────────────────────────┤│ Shared inbox: │ https://.lhr.life/inbox │╰───────────────┴─────────────────────────────────────────╯Web interface available at: http://localhost:8000/.lhr.life/r/2 │╰────────────────┴─────────────────────────────────────╯
Holy wall of text, Batman!
The Allure of the Fediverse and the Challenges of ActivityPub Implementation
The fediverse—a decentralized social web powered by protocols like ActivityPub—is an exciting space to explore. If you’re considering building the next great federated app, connected to platforms like Mastodon, Lemmy, Pixelfed, and more, you might be tempted to implement ActivityPub from scratch. While this approach offers total control, it’s a daunting task due to the complexity of the standards involved.
The Challenge: Data Modeling with ActivityStreams & JSON-LD
At its core, ActivityPub relies on ActivityStreams 2.0 vocabulary and JSON-LD syntax. This combination introduces significant complexity:
Understanding ActivityStreams Vocabulary:
You need to model actions and objects (e.g., posts as
Note
orArticle
, profiles asPerson
orOrganization
, and actions likeCreate
,Follow
,Like
,Announce
) using the precise terms defined in the specification.JSON-LD Specifics:
JSON-LD has unique rules that complicate direct JSON manipulation:
// No name property { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "…"} // Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "name": [], "content": "…"}
// Single value { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "Hello"} // Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": ["Hello"]}
// Embedded object { "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", "actor": { "type": "Person", "id": "http://sally.example.org/", "name": "Sally" }, "object": { "type": "Arrive", "id": "https://sally.example.com/arrive", /* ... */ } } // Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", "actor": "http://sally.example.org/", "object": "https://sally.example.com/arrive" }
Fedify: Simplifying ActivityPub Development
Fedify, a TypeScript framework, abstracts the complexity of ActivityPub development. It handles the heavy lifting, allowing you to focus on what makes your app unique.
Example Code with Fedify
Fedify simplifies federation logic:
// Handle follow federation.on(Follow, async (ctx, follow) => { // Implement follow logic }); // Handle post activity federation.on(Create, async (ctx, activity) => { // Implement post activity logic });
Web Interface and Logs
When running your app:
Logs example:
Conclusion
Building a federated app with ActivityPub is challenging, but tools like Fedify make the process manageable. By abstracting away the complexity of JSON-LD and ActivityStreams, Fedify allows developers to focus on creating innovative applications for the fediverse.