github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/pkg/mux/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha512" 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "net" 11 12 "github.com/bitfinexcom/bitfinex-api-go/pkg/models/event" 13 "github.com/bitfinexcom/bitfinex-api-go/pkg/mux/msg" 14 "github.com/bitfinexcom/bitfinex-api-go/pkg/utils" 15 "github.com/gobwas/ws" 16 "github.com/gobwas/ws/wsutil" 17 ) 18 19 type Client struct { 20 id int 21 conn net.Conn 22 nonceGen *utils.EpochNonceGenerator 23 subsLimit int 24 subs map[event.Subscribe]bool 25 } 26 27 // New returns pointer to Client instance 28 func New() *Client { 29 return &Client{ 30 subs: make(map[event.Subscribe]bool), 31 nonceGen: utils.NewEpochNonceGenerator(), 32 } 33 } 34 35 // WithID assigns clinet ID 36 func (c *Client) WithID(ID int) *Client { 37 c.id = ID 38 return c 39 } 40 41 // WithSubsLimit sets limit of subscriptions on the instance 42 func (c *Client) WithSubsLimit(limit int) *Client { 43 c.subsLimit = limit 44 return c 45 } 46 47 // Public creates and returns client to interact with public channels 48 func (c *Client) Public(url string) (*Client, error) { 49 conn, _, _, err := ws.DefaultDialer.Dial(context.Background(), url) 50 if err != nil { 51 return nil, err 52 } 53 c.conn = conn 54 return c, nil 55 } 56 57 // Private creates and returns client to interact with private channels 58 func (c *Client) Private(key, sec, url string, dms int) (*Client, error) { 59 nonce := c.nonceGen.GetNonce() 60 conn, _, _, err := ws.DefaultDialer.Dial(context.Background(), url) 61 if err != nil { 62 return nil, err 63 } 64 65 c.conn = conn 66 payload := "AUTH" + nonce 67 sig := hmac.New(sha512.New384, []byte(sec)) 68 if _, err := sig.Write([]byte(payload)); err != nil { 69 return nil, err 70 } 71 72 pldSign := hex.EncodeToString(sig.Sum(nil)) 73 sub := event.Subscribe{ 74 Event: "auth", 75 APIKEY: key, 76 AuthSig: pldSign, 77 AuthPayload: payload, 78 AuthNonce: nonce, 79 DMS: dms, 80 } 81 82 if err := c.Subscribe(sub); err != nil { 83 return nil, err 84 } 85 return c, nil 86 } 87 88 // Subscribe takes subscription payload as per docs and subscribes client to it. 89 // We keep track of subscriptions so that when client failes, we can resubscribe. 90 func (c *Client) Subscribe(sub event.Subscribe) error { 91 if err := c.Send(sub); err != nil { 92 return err 93 } 94 95 c.AddSub(sub) 96 return nil 97 } 98 99 // Unsubscribe takes channel id and unsubscribes client from it. 100 func (c *Client) Unsubscribe(chanID int64) error { 101 pld := struct { 102 Event string `json:"event"` 103 ChanID int64 `json:"chanId"` 104 }{ 105 Event: "unsubscribe", 106 ChanID: chanID, 107 } 108 109 if err := c.Send(pld); err != nil { 110 return err 111 } 112 113 return nil 114 } 115 116 // Send takes payload in form of interface and sends it to api 117 func (c *Client) Send(pld interface{}) error { 118 b, err := json.Marshal(pld) 119 if err != nil { 120 return err 121 } 122 123 return wsutil.WriteClientBinary(c.conn, b) 124 } 125 126 // Close closes the socket connection 127 func (c *Client) Close() error { 128 return c.conn.Close() 129 } 130 131 // Read starts consuming data stream 132 func (c *Client) Read(ch chan<- msg.Msg) { 133 defer c.conn.Close() 134 135 for { 136 ms, opCode, err := wsutil.ReadServerData(c.conn) 137 m := msg.Msg{Data: ms, CID: c.id} 138 139 if err != nil { 140 m.Err = err 141 ch <- m 142 return 143 } 144 145 if opCode == ws.OpClose { 146 m.Err = errors.New("client has closed unexpectedly") 147 ch <- m 148 return 149 } 150 151 ch <- m 152 } 153 } 154 155 // SubsLimitReached returns true if number of subs > subsLimit 156 func (c *Client) SubsLimitReached() bool { 157 if c.subsLimit == 0 { 158 return false 159 } 160 return len(c.subs) == c.subsLimit 161 } 162 163 // SubAdded checks if given subscription is already added. Used to 164 // avoid duplicate subscriptions per client 165 func (c *Client) SubAdded(sub event.Subscribe) (isAdded bool) { 166 _, isAdded = c.subs[sub] 167 return 168 } 169 170 // AddSub adds new subscription to the list 171 func (c *Client) AddSub(sub event.Subscribe) { 172 c.subs[sub] = true 173 } 174 175 // RemoveSub removes new subscription to the list 176 func (c *Client) RemoveSub(sub event.Subscribe) { 177 delete(c.subs, sub) 178 } 179 180 // GetAllSubs returns all subscriptions 181 func (c *Client) GetAllSubs() (res []event.Subscribe) { 182 for sub := range c.subs { 183 res = append(res, sub) 184 } 185 return 186 }