- Go 73.9%
- TypeScript 16.1%
- CSS 7.8%
- HTML 1.3%
- Dockerfile 0.9%
| auth | ||
| fetchers | ||
| frontend | ||
| general | ||
| notes | ||
| .gitignore | ||
| air.toml | ||
| compose.yml | ||
| Dockerfile | ||
| env.example | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
Disclaimer
I do intend for this to be a usable client, and I intend to use it on my day to day, but this project has been born first and foremost as a way for me to get familiar with the Go and TypeScript languages, so I cannot give a full guarantee of correctness or security. Use it at your own risk.
(I will remove this disclaimer once it reaches a certain point of maturity where I can be confident in giving said guarantee and I'm willing to take responsibility if it does not meet expectations.)
Contributions and suggestions welcome. Let me know if you think there is a best practice I am missing in the code, or any tip for improvement.
Behind the name
"Llostre" is a Catalan word meaning "twilight" or "dusky", and is pronounced [ˈʎɔs.tɾə] in my dialect. It's a rarer variant of the more common Catalan word "crepuscle".
I learnt it on a Catalan localization of Your Name (君の名は。) and found it beautiful. And, just like twilight sits between this world and the next, this app sits in the space between the Bluesky network and the wider Atmosphere network.
Design Goals
This project has some very specific design goals that have informed it's architecture and design. The first and foremost, not being yet another social-app fork.
1. Independent from Bluesky LLC infrastructure
I want this project to be resilient to a doomsday scenario of all Bluesky infrastructure suddenly dissapearing one day. One option would be to offer llostre as part of a stack that included its own AppView and Relay, but because of the other design goals, I chose the other option: to go AppView-less.
2. Lightweight & selfhost-friendly
I want this to be as lean as possible, specially storage-wise. I am aware microcosm's creator has fit the entirety of the Bluesky network in a Raspberry Pi, but I wanted this to fit in even smaller places. I know what it's like to count each available gigabyte for selfhosting in very small machines; so I wanted only to keep posts for a while, and only the most relevant ones. This obviously makes the app load slower, specially on a first-time load, but I think this is an acceptable trade-off.
3. Be more than a Bluesky client: be an Atmosphere client
I want the timeline to show not only Bluesky posts, but also other types of records that could be easily translated into post-looking elements. I want this to give a sneak peek to the actual Atmosphere network, and not just limit itself to Bluesky.
4. Some other miscellaneous aesthetic decisions
As a long time Tumblr user, I prefer when I can see the full context for a last reply by being able to read the entire thread in one go, even if it means a risk of having repetitive threads with many different dangling replies from the same person I follow. I hope you do love the color of the sky.
I also wanted to incorporate unofficial Markdown rendering to Bluesky posts, partially inspired by some Mastodon forks that also do that unlike vanilla Mastodon.
Not necessarily a goal, but as a compromise between infinite scroll and paged scrolling, the timeline will have a "load more" button once you reach the "end" (for ease of implementation, with how the post caching works.)
Architecture
AppView-less record cache
There are two ways that post storage is aggressively kept at a minimum. First, posts are not stored persistently; they are stored on a cache with an expiration timer. Whenever a record is requested from the cache, the expiration timer is reset, so records that are accessed often will be kept around for longer.
"Caroming"-style record fetching
The other way is by how records are requested and stored. First, the most recent records from your followings are requested. In turn, if any of these records reference another one (because they're a reply, or a like, or a repost, or a quote, etc), then those specific records are requested from their authors. If an optional Slingshot endpoint is configured, this will be hit first, but if this fails or is not configured, it will fall back to requesting the PDSs directly.
If the profile of a "cold" repo (one that is not among the followings of any llostre user, and has not had its most recent records put into the cache) is accessed by anyone, then the most recent records will be requested in order to populate the profile page. (The profile page hasn't been implemented yet)
The cache also keeps track of what was the last batch of requested records for each repo, so it will keep requesting new batches as you scroll and "run out" of cached posts to see.
While the app is running, it will also monitor the jetstream in orden to update or invalidate any updated or deleted records currently stored in the cache. However, to avoid doing convoluted backfill tasks on startup, the cache will just get flushed at startup of any remaining records. Llostre is intended to be hosted somewhat permanently on a server, so startups aren't expected to be frequent enough for this to be too big of a problem.
Working on
- Monitoring the jetstream
To Do
- Drop posts from people who have blocked you
- Be able to like posts
- Be able to repost
- See reposts
- See likes?
- Be able to post
- Implement profile pages
- Implement post pages, interaction counts and replies
- Implement settings
- Implement a sidebar
Related kanban board: https://skyboard.dev/board/did:plc:hyyqbqqhpxt2bnn7s2e2iuxd/3mqpa2sbvtc2d
Things I've learnt with this project
- Go
- TypeScript?
- OAuth
- That
/xrpc/com.atproto.server.getSessiondoes not work with OAuth