Send updates about (for now, only) blocks via WebSocket.
The indexer handles subscriptions using the Subs struct, which allows to get tokio::sync::broadcast::Receivers for BlockMsg. When a block is connected/disconnected/finalized, the indexer tells Subs to broadcast a BlockMsg to all the subscribers.
In the indexer, we put the Subs behind a separate RwLock, as adding subscribers will need &mut access. Since we don't want to lock the entire indexer for adding a single WS subscription (potential DoS vector), we put it behind a separate lock.
When a client connects via WebSocket, we handle that connection's livecycle in handle_subscribe_socket. We listen to both messages from the client as well as from the indexer using select!. To separate the ws handling and the subscription logic, we add WsMsg.
Errors (e.g. the client sending an unknown/invalid message) are handled by sending the client a proto::Error message.
For testing, we add websocket-client via pip, and add a ws method to ChronikClient.