github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/pkg/mux/mux.go (about) 1 package mux 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "sync" 8 "time" 9 10 "github.com/bitfinexcom/bitfinex-api-go/pkg/models/event" 11 "github.com/bitfinexcom/bitfinex-api-go/pkg/mux/client" 12 "github.com/bitfinexcom/bitfinex-api-go/pkg/mux/msg" 13 ) 14 15 // Mux will manage all connections and subscriptions. Will check if subscriptions 16 // limit is reached and spawn new connection when that happens. It will also listen 17 // to all incomming client messages and reconnect client with all its subscriptions 18 // in case of a failure 19 type Mux struct { 20 cid int 21 dms int 22 publicChan chan msg.Msg 23 publicClients map[int]*client.Client 24 privateChan chan msg.Msg 25 closeChan chan bool 26 privateClient *client.Client 27 mtx *sync.RWMutex 28 Err error 29 transform bool 30 apikey string 31 apisec string 32 subInfo map[int64]event.Info 33 authenticated bool 34 publicURL string 35 authURL string 36 online bool 37 rateLimitQueueSize int 38 } 39 40 // api rate limit is 20 calls per minute. 1x3s, 20x1min 41 const ( 42 rateLimitDuration = 3 * time.Second 43 maxRateLimitQueueSize = 20 44 ) 45 46 // New returns pointer to instance of mux 47 func New() *Mux { 48 return &Mux{ 49 publicChan: make(chan msg.Msg), 50 privateChan: make(chan msg.Msg), 51 closeChan: make(chan bool), 52 publicClients: make(map[int]*client.Client), 53 mtx: &sync.RWMutex{}, 54 subInfo: map[int64]event.Info{}, 55 publicURL: "wss://api-pub.bitfinex.com/ws/2", 56 authURL: "wss://api.bitfinex.com/ws/2", 57 } 58 } 59 60 // TransformRaw enables data transformation and mapping to appropriate 61 // models before sending it to consumer 62 func (m *Mux) TransformRaw() *Mux { 63 m.transform = true 64 return m 65 } 66 67 // WithAPIKEY accepts and persists api key 68 func (m *Mux) WithAPIKEY(key string) *Mux { 69 m.apikey = key 70 return m 71 } 72 73 // WithDeadManSwitch - when socket is closed, cancel all account orders 74 func (m *Mux) WithDeadManSwitch() *Mux { 75 m.dms = 4 76 return m 77 } 78 79 // WithAPISEC accepts and persists api secret 80 func (m *Mux) WithAPISEC(sec string) *Mux { 81 m.apisec = sec 82 return m 83 } 84 85 // WithPublicURL accepts and persists public api url 86 func (m *Mux) WithPublicURL(url string) *Mux { 87 m.publicURL = url 88 return m 89 } 90 91 // WithAuthURL accepts and persists auth api url 92 func (m *Mux) WithAuthURL(url string) *Mux { 93 m.authURL = url 94 return m 95 } 96 97 func (m *Mux) IsConnected() bool { 98 return m.online 99 } 100 101 func (m *Mux) Close() bool { 102 m.closeChan <- true 103 return true 104 } 105 106 // Subscribe - given the details in form of event.Subscribe, subscribes client to public 107 // channels. If rate limit is reached, calls itself recursively after 1s with same params 108 func (m *Mux) Subscribe(sub event.Subscribe) *Mux { 109 if m.Err != nil { 110 return m 111 } 112 113 // if limit is reached, wait 1 second and recuresively 114 // call Subscribe again with same subscription details 115 if m.rateLimitQueueSize == maxRateLimitQueueSize { 116 time.Sleep(1 * time.Second) 117 return m.Subscribe(sub) 118 } 119 120 m.mtx.RLock() 121 defer m.mtx.RUnlock() 122 if m.publicClients[m.cid].SubAdded(sub) { 123 return m 124 } 125 126 if m.Err = m.publicClients[m.cid].Subscribe(sub); m.Err != nil { 127 return m 128 } 129 130 if limitReached := m.publicClients[m.cid].SubsLimitReached(); limitReached { 131 log.Printf("subs limit is reached on cid: %d, spawning new conn\n", m.cid) 132 m.addPublicClient() 133 } 134 135 m.rateLimitQueueSize++ 136 return m 137 } 138 139 // Start creates initial clients for accepting connections 140 func (m *Mux) Start() *Mux { 141 if m.hasAPIKeys() && m.privateClient == nil { 142 m.addPrivateClient() 143 } 144 145 m.watchRateLimit() 146 m.addPublicClient() 147 return m 148 } 149 150 // Listen accepts a callback func that will get called each time mux 151 // receives a message from any of its clients/subscriptions. It 152 // should be called last, after all setup calls are made 153 func (m *Mux) Listen(cb func(interface{}, error)) error { 154 if m.Err != nil { 155 return m.Err 156 } 157 158 m.online = true 159 for { 160 select { 161 case ms, ok := <-m.publicChan: 162 if !ok { 163 return errors.New("public channel has closed unexpectedly") 164 } 165 if ms.Err != nil { 166 cb(nil, fmt.Errorf("conn:%d has failed | err:%s | reconnecting", ms.CID, ms.Err)) 167 m.resetPublicClient(ms.CID) 168 continue 169 } 170 // return raw payload data if transform is off 171 if !m.transform { 172 cb(ms.Data, nil) 173 continue 174 } 175 // handle event type message 176 if ms.IsEvent() { 177 cb(m.recordEvent(ms.ProcessEvent())) 178 continue 179 } 180 // handle data type message 181 if ms.IsRaw() { 182 raw, pld, chID, _, err := ms.PreprocessRaw() 183 if err != nil { 184 cb(nil, err) 185 continue 186 } 187 188 inf, ok := m.subInfo[chID] 189 if !ok { 190 cb(nil, fmt.Errorf("unrecognized chanId:%d", chID)) 191 continue 192 } 193 cb(ms.ProcessPublic(raw, pld, chID, inf)) 194 continue 195 } 196 cb(nil, fmt.Errorf("unrecognized msg signature: %s", ms.Data)) 197 case ms, ok := <-m.privateChan: 198 if !ok { 199 return errors.New("private channel has closed unexpectedly") 200 } 201 if ms.Err != nil { 202 cb(nil, fmt.Errorf("err: %s | reconnecting", ms.Err)) 203 m.resetPrivateClient() 204 continue 205 } 206 // return raw payload data if transform is off 207 if !m.transform { 208 cb(ms.Data, nil) 209 continue 210 } 211 // handle event type message 212 if ms.IsEvent() { 213 cb(m.recordEvent(ms.ProcessEvent())) 214 continue 215 } 216 // handle data type message 217 if ms.IsRaw() { 218 raw, pld, chID, msgType, err := ms.PreprocessRaw() 219 if err != nil { 220 cb(nil, err) 221 continue 222 } 223 cb(ms.ProcessPrivate(raw, pld, chID, msgType)) 224 continue 225 } 226 cb(nil, fmt.Errorf("unrecognized msg signature: %s", ms.Data)) 227 case <-m.closeChan: 228 m.mtx.Lock() 229 defer m.mtx.Unlock() 230 231 for _, v := range m.publicClients { 232 if v == nil { 233 continue 234 } 235 if err := v.Close(); err != nil { 236 log.Printf("failed closing public client: %s\n", err) 237 } 238 } 239 240 if m.privateClient != nil { 241 if err := m.privateClient.Close(); err != nil { 242 log.Printf("failed closing private client: %s\n", err) 243 } 244 } 245 246 m.online = false 247 return nil 248 } 249 } 250 } 251 252 // Send meant for authenticated input, takes payload in form of interface 253 // and calls client with it 254 func (m *Mux) Send(pld interface{}) error { 255 if !m.authenticated || m.privateClient == nil { 256 return errors.New("not authorized") 257 } 258 return m.privateClient.Send(pld) 259 } 260 261 func (m *Mux) hasAPIKeys() bool { 262 return len(m.apikey) != 0 && len(m.apisec) != 0 263 } 264 265 func (m *Mux) recordEvent(i event.Info, err error) (event.Info, error) { 266 switch i.Event { 267 case "subscribed": 268 m.subInfo[i.ChanID] = i 269 case "auth": 270 if i.Status == "OK" { 271 m.subInfo[i.ChanID] = i 272 m.authenticated = true 273 } 274 } 275 // add more cases if/when needed 276 return i, err 277 } 278 279 func (m *Mux) resetPublicClient(cid int) { 280 // pull old client subscriptions 281 subs := m.publicClients[cid].GetAllSubs() 282 // add fresh client 283 m.addPublicClient() 284 // resubscribe old events 285 for _, sub := range subs { 286 log.Printf("resubscribing: %+v\n", sub) 287 m.Subscribe(sub) 288 } 289 // remove old, closed channel from the list 290 delete(m.publicClients, cid) 291 } 292 293 func (m *Mux) resetPrivateClient() { 294 m.authenticated = false 295 m.privateClient = nil 296 m.addPrivateClient() 297 } 298 299 func (m *Mux) addPublicClient() *Mux { 300 // adding new client so making sure we increment cid 301 m.cid++ 302 // create new public client and pass error to mux if any 303 c, err := client. 304 New(). 305 WithID(m.cid). 306 WithSubsLimit(30). 307 Public(m.publicURL) 308 if err != nil { 309 m.Err = err 310 return m 311 } 312 // add new client to list for later reference 313 m.publicClients[m.cid] = c 314 // start listening for incoming client messages 315 go c.Read(m.publicChan) 316 return m 317 } 318 319 func (m *Mux) addPrivateClient() *Mux { 320 // create new private client and pass error to mux if any 321 c, err := client.New().Private(m.apikey, m.apisec, m.authURL, m.dms) 322 if err != nil { 323 m.Err = err 324 return m 325 } 326 327 m.privateClient = c 328 go c.Read(m.privateChan) 329 return m 330 } 331 332 // watchRateLimit will run once every rateLimitDuration 333 // and free up the queue 334 func (m *Mux) watchRateLimit() { 335 go func() { 336 for { 337 if m.rateLimitQueueSize > 0 { 338 m.rateLimitQueueSize-- 339 } 340 341 time.Sleep(rateLimitDuration) 342 } 343 }() 344 }