Skip to content

Invites and requests in the Atmosphere?

Fred Jawa
Nov 7, 20267 min read
1

've been working on a little ATProto project recently and came across a pattern that I haven't seen implemented or solved anywhere that I'm aware of. I'm a big fan of the Mina Kimes Show (Featuring Lenny), a football podcast that has a delightful balance of nerding out about advanced statistics and going "holy shit did you see that throw that was insane". Every year, Mina and guest Mike Golic Jr. do a competition that they call a "team draft" where they take turns picking teams based on how well they think those teams will perform in the upcoming season using a particular scoring system.

I've been looking for potential ATProto apps to contribute to the ecosystem, especially ones that are usable without needing a network of other people using the app (i.e. they don't rely heavily on social features and content coming from other users). A team draft app seemed like it fit the bill, as each league is completely self-contained (though I suppose something like a global leaderboard or average draft positions for each team across all users could also be fun). Even better, it requires only minimal data from any external sources: the list of all the teams (which is static) and the scores (which update ~3 days a week). I figured I could get this up and running fairly quickly, before the football season started, so I starting working on it.

That was a few months ago. I made a lot of progress on the app in a short amount of time (a week or two) and then got sidetracked looking for a new job. C'est la vie. But! Now I have a new job and some time and energy, so I'm returning to the project in the hopes that it will still be enjoyable even if the league starts part way through the season. As part of that, I was reminded that I ran into an interesting problem early in the dev process that I wanted to surface for discussion with knowledgeable ATProto folks. Hence, this post.

Here's the situation. One thing that really interests me about building on ATProto is the ability to simplify which parts of the stack I need to manage, since the PDS itself provides storage/persistence. For this project, I wanted to explore how far I could push that idea. Could I build this without any sort of backend/AppView layer at all? Could I do this entirely with a frontend app that communicated directly with the PDS? I had already worked on something similar with my SkyVault project, where it worked perfectly well. Could I extend that here?

I quickly ran into a major problem that SkyVault never had to contend with: creating leagues (i.e. groups) of users. SkyVault is only for a single user, reading/editing data from a single PDS. I had users log-in using an app password for simplicity, which gives the app all the necessary permissions to read from and write to the user's PDS. For the team draft, I specifically need to be able to create groups of users who will draft from the same pool and compete against each other, but the app only has permissions to write to the PDS of the logged-in user. The league data is created in the PDS of the league owner - simple enough. How does that user then go about adding other users to the league? The intuitive way (not thinking about permissions at all) would be for the owner to write an invitation record into the PDS of the player they want to invite. Then, the next time that player logs in, they would see the invite and either accept or reject it, which would write a record back into the PDS of the league owner to update the status. Out of the box, however, this isn't possible due to the lack of cross-PDS permissions - the league owner can only write to their own PDS, not the other user they want to invite, and vice versa.

I ended up solving this (at least temporarily) by going outside the protocol. The league owner sends an invite link to the other user via some other communication channel, and when the second user opens the link and logs in it creates the record in their PDS (for example, this record). Then, they get a link to send back to the league owner that updates the membership list in the league record itself (example). This is a bit clunky, but it solves the problem while also satisfying some other requirements that I felt were important, namely that people have to be invited to be able to join a league and people have to accept the invite to be added to a league. The membership record in the non-owner PDS basically acts a a consent to be added to the league, but it doesn't do anything until the membership list in the league owner PDS is updated as well. Once this is all set up, the league owner PDS contains the league info, whereas the PDS for each other player contains just the picks they made in the draft.

Again, this works, but it feels clunky and not fully Atmospheric. I wanted to explore some other ways of doing this and ask around to see if anyone has solved this issue in a way I haven't thought of. This example is fairly trivial - how much does it really matter if I get added to a team draft league without consenting to it? - but I think it's an interesting problem with other potential applications that are more impactful. For example, adding private accounts to a new Bluesky client that require a request and approval to allow new followers. Same exact workflow, but less trivial since Bluesky feeds can be much more personal and private.

The basic requirements are, as described above, a workflow that both parties need to consent before a change is made (adding user to a league, allowing a follow on a private account, etc). So, with all that in mind, here are the ideas I'm exploring:

  • Invite links (as described above)
    • Minimal infra, works with just frontend and PDS
    • Clunky, relies on users already having a separate means of communication
      • I.e. this probably doesn't work as well for the follow requests use case
    • Could also potentially do the same pattern but by listening to the JetStream and with each user receiving updates as they are posted
      • Seems like a lot of event consuming when you know you're really only looking for one specific update, but is more fully Atmospheric
  • Add an app-specific backend/AppView
    • In this case, the backend would be able to hold the invite in its own persistence layer until accepted, at which point it would write to the PDSs
    • Probably the most straightforward solution
    • Provides other potential benefits in terms of ability to aggregate data
    • Extra infra to maintain for every app
  • Invites-as-a-Service
    • One service that could provide this backend utility for any number of different apps, holding the invites in a persistence layer until the user logs in to accept them
    • Would need to have permissions to edit across many different types of records
    • E.g. for the follow request use case, my new Bluesky client creates a follow request record in my PDS and calls to the Invite-aaS provider to store the request in their system. The Bluesky client for the profile I've requested to follow checks the Invite-aaS provider to see if there are any outstanding invites and surfaces them for acceptance/rejection. Once accepted, it creates a record in the followed-user PDS to confirm the two-party consent, allowing me to see the content
  • Some sort of new PDS functionality that would allow this directly
    • I don't know how possible this is or really what it would look like, to be honest
    • I'm envisioning some sort of inbox that other users can write to within my PDS
    • Obviously a lot of security concerns here

So that's where I'm at. Only the last one of these (and maybe the JetStream version of the first option) is truly "on protocol". I would love to hear from folks who have been working on ATProto apps about how they've solved this problem. Is there a solution that I'm missing here? Is this just something we'll accept and live with, since many apps are going to run some sort of backend anyway? Is this just not that important of a use case? Would other folks find value in the shared invites/requests service and is that worth doing more work on? Let me know!

Did you enjoy this article?

Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.

Across the AtmosphereDiscussions