Suvam

io

Deb's Blog

Architecture of Dhaaga's Bridge Platform

January 5, 2025

The bridge library used inside the Dhaaga project was single-handedly the largest technical hurdle while building the project.

The Problem

While the fediverse (ActivityPub/AtProto) is de-centralized, the client ecosystem for it is mostly centralized.

As an end user, the first question one asks when a new project pops up is:

Will this work with my Mastodon/Bluesky app ?

While installing PWAs seems to be the most accepted compromise in the community, the difference in experience between a resource hungry web app and a smooth 120Hz native app with native features can be very apparent.

This was one of my major gripes about the fediverse, which I struggle with to this day while switching between plaatforms to read my daily feeds. I did not want the Dhaaga project to be limited by this problem.

So, I set out to build a wrapper platform that would allow a (javascript) frontend to consume platform/protocol APIs using a well-defined asbtraction layer, wiithout having to worry about the internals.

The Scope

While daily interactions like browsing posts and profiles is mostly the across every social media platform, certain other features like moderation are often platform specific.

Also, there are certain features (e.g. - atproto labellers, misskey reactions) which add flavor to the user experience, and not something we can compromise on.

Progress So Far

The latest version of the monorepo (v0.19.3) fully integrates the features listed above against the following major platforms and forks:

  • Bluesky (via @atproto/api)
  • Mastodon (via masto)
  • Misskey (+Sharkey, +CherryPick, via misskey-js)
  • Pleroma (+Akkoma, via megalodon)
  • …while using fetch directly, for missing implementations and forks.

I will be adding Tumblr support in the near future.

Other platforms would depend on user demand (GoToSocial, Friendica, etc.), API availability (X, Threads) and personal interest across other domains (Lemmy, Nostr).

The Approach

// For objects
async function getEntityInterface(): Promise<UserInterface> {
  const resultA = await client.accounts.get("123") // fetch a profile
  if (resultA.isErr()) return // handle errors
  return UserParser.rawToInterface<unknown>(resultA.unwrap(), driver)
}

// For arrays
async function getEntityInterfaceArray(query: any): Promise<UserInterface> {
  const resultA = await client.accounts.likes(query) // fetch a profile
  if (resultA.isErr()) return // handle errors
  return UserParser.rawToInterface<unknown[]>(resultA.unwrap(), driver)
}