github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/rpc/core/events.go (about) 1 package core 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/pkg/errors" 8 9 tmpubsub "github.com/tendermint/tendermint/libs/pubsub" 10 tmquery "github.com/tendermint/tendermint/libs/pubsub/query" 11 ctypes "github.com/tendermint/tendermint/rpc/core/types" 12 rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" 13 ) 14 15 const ( 16 // Buffer on the Tendermint (server) side to allow some slowness in clients. 17 subBufferSize = 100 18 ) 19 20 // Subscribe for events via WebSocket. 21 // More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe 22 func Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { 23 addr := ctx.RemoteAddr() 24 25 if env.EventBus.NumClients() >= env.Config.MaxSubscriptionClients { 26 return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients) 27 } else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient { 28 return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient) 29 } 30 31 env.Logger.Info("Subscribe to query", "remote", addr, "query", query) 32 33 q, err := tmquery.New(query) 34 if err != nil { 35 return nil, errors.Wrap(err, "failed to parse query") 36 } 37 38 subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout) 39 defer cancel() 40 41 sub, err := env.EventBus.Subscribe(subCtx, addr, q, subBufferSize) 42 if err != nil { 43 return nil, err 44 } 45 46 // Capture the current ID, since it can change in the future. 47 subscriptionID := ctx.JSONReq.ID 48 go func() { 49 for { 50 select { 51 case msg := <-sub.Out(): 52 resultEvent := &ctypes.ResultEvent{Query: query, Data: msg.Data(), Events: msg.Events()} 53 ctx.WSConn.TryWriteRPCResponse( 54 rpctypes.NewRPCSuccessResponse( 55 ctx.WSConn.Codec(), 56 subscriptionID, 57 resultEvent, 58 )) 59 case <-sub.Cancelled(): 60 if sub.Err() != tmpubsub.ErrUnsubscribed { 61 var reason string 62 if sub.Err() == nil { 63 reason = "Tendermint exited" 64 } else { 65 reason = sub.Err().Error() 66 } 67 ctx.WSConn.TryWriteRPCResponse( 68 rpctypes.RPCServerError( 69 subscriptionID, 70 fmt.Errorf("subscription was cancelled (reason: %s)", reason), 71 )) 72 } 73 return 74 } 75 } 76 }() 77 78 return &ctypes.ResultSubscribe{}, nil 79 } 80 81 // Unsubscribe from events via WebSocket. 82 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe 83 func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { 84 addr := ctx.RemoteAddr() 85 env.Logger.Info("Unsubscribe from query", "remote", addr, "query", query) 86 q, err := tmquery.New(query) 87 if err != nil { 88 return nil, errors.Wrap(err, "failed to parse query") 89 } 90 err = env.EventBus.Unsubscribe(context.Background(), addr, q) 91 if err != nil { 92 return nil, err 93 } 94 return &ctypes.ResultUnsubscribe{}, nil 95 } 96 97 // UnsubscribeAll from all events via WebSocket. 98 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe_all 99 func UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { 100 addr := ctx.RemoteAddr() 101 env.Logger.Info("Unsubscribe from all", "remote", addr) 102 err := env.EventBus.UnsubscribeAll(context.Background(), addr) 103 if err != nil { 104 return nil, err 105 } 106 return &ctypes.ResultUnsubscribe{}, nil 107 }