-
@ Water Blower
2024-04-24 12:36:55I am the author of Blowater, a Discord style nostr client.
Here is a list of my learnings after 15 months of DM focusing development.
I have used Blowater for more than 10,000 DMs so there is some credibility of my findings.
1. 100% Delivery is the most important problem
For example, if user1 wants to send messages to user2, they have to connect to at least 1 common relay. For whatever reasons, if they are not on the same relay, even just for several minutes, some messages will be missing.
It is fine for a user to not see all kind-1s, aka social media. Partial discovery/delivery is how Nostr is designed originally.
But for kind-4s, aka directed messages, this problem is a deal breaker. It's more critical than meta data leak and other privacy/security problems. If you can't deliver your message, it's useless to have a secure message.
Therefore, I believe that while problems like meta data leaks and authentications are important, it's less prioritized than the delivery problem.
Ideas such as Inbox Model are more urgent at this moment.
But, before we step into these discussions, we need to clearly define our design/architecture boundary.
We can at least divide solutions to 4 categories:
| Head | Single Client | Cross Client | | --- | --- | --- | | Single Relay | Centralized | Semi-centralized | | Cross Relay | Slack style | Decentralized |
Now, the problem is, can we achieve 100% delivery +
cross client
+cross relay
at the same time?Because 100% delivery is not compromisable, if we have to sacrifice, should we give up
cross client
orcross relay
?In my opinion, we should sacrifice
cross client
and keepcross relay
because the client is the most influential place to ensure a good user experience.Blowater used to work
cross client
+cross relay
with the original NIP-4. For rational described above, Blowater changed tosingle client
+single relay
with the adoption of NIP-44. Yes, the DM of Blowater is pretty much centralized at this moment because we have not figured out a reliable way of delivering messages cross relays.That's why I look forward to inbox-mode.
2. Offline mode and working with bad relays are necessary
If you are on a bad network condition, you still want to browse messages and potentially search them on your device. People usually message themselves as a clever way to take notes and reminders. In fact, it is 10X more useful and convenient than specialized note taking & reminder apps.
Because of this design & engineering goal, Blowater stores all events locally and never deletes. Searching through half a millions notes (including kind-1) only takes a few milliseconds.
As a side effect, Blowater does not need NIP-50 to have a proper search. NIP-50 is nice to have but not a necessity.
The same design & engineering choice also applies to working with bad relays that either do not implement all the NIPs this client needs or return data in an incorrect or inconvenient way.
Relying on the authority of servers is the mental model of a centralized world. Because nostr events are immutable, data stored in clients are not cache. They are the source of truth as well. Therefore, storing as many events locally as possible is a good thing that makes the client both faster and independent from network conditions.
Clients can be seen as relays with UIs and only stores events relevant to the logged in npub. Relays can be seen as clients that have no UIs and stores events of many npubs.
WebSocket-only is a horrible design choice
There are 2 aspects of this statement.
The first being that WebSocket is a streaming API and streaming API for everything is a bad choice. Mainly for 2 use cases:
- get event by ID
Nostr events are immutable, if I have the ID of an event, it does not change. It makes no sense to have a stream of only 1 thing. A relay either has this event or it does not. A HTTP GET of 200 / 404 is much better.
Supporting HTTP API does not make client nor relay implementations harder. WebSocket relies on HTTP/1.1. If you support WebSocket, you have to support HTTP in the first place.
- post events to relays
A simple HTTP POST is a much better way to send data to relays. It makes ad-hoc writing much simpler. The client does not need to establish a WebSocket connection. It's more performant.
To summarize, streaming API is for working with unknown size, possibly infinite data (either in size or in time). If the data size is known & finite, request/response API is much better.
The second aspect is that WebSocket is not a good streaming protocol, at least for the web browsers. There is no way to force close/kill a WebSocket connection on the client side if the server is offline.
For example, client A connects to relay B at time X. At time X + 1, A sends a disconnection message to relay B which was down for whatever reason. Client A will wait there forever and the WebSocket connection, at least from client's perspective, will never be closed.
The WebSocket specification requires the client to wait for the server acknowledgement of closing the connection which prevents client from force closing.
Therefore, it's pretty much impossible to have a reliable browser client that connects to many relays and works reliably for long days. People tend to close their browser tabs so it's not likely to happen but it's still a fundamental flaw.
Additionally, because WebSocket is HTTP/1.1, it does not have all the goodies that HTTP/3 might give us. This is not a problem at the moment. But if we want to have a future proof system, we need future proof design.
WebSocket & many Web clients might serve as a nice starting point to bootstrap the ecosystem. But we can't stay here forever. We have to grow out.
Here you go, 3 main takes away I have about Nostr development. It's not all the learning that I have but I believe they are the most relevant for many developers.