github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/v2/websocket/channels.go (about)

     1  package websocket
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
     9  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/balanceinfo"
    10  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingcredit"
    11  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundinginfo"
    12  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingloan"
    13  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingoffer"
    14  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingtrade"
    15  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/margin"
    16  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/notification"
    17  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/order"
    18  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/position"
    19  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/tradeexecution"
    20  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/tradeexecutionupdate"
    21  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/wallet"
    22  )
    23  
    24  type Heartbeat struct {
    25  	//ChannelIDs []int64
    26  }
    27  
    28  func (c *Client) handleChannel(socketId SocketId, msg []byte) error {
    29  	if c.terminal {
    30  		return fmt.Errorf("received a message after close")
    31  	}
    32  
    33  	var raw []interface{}
    34  	err := json.Unmarshal(msg, &raw)
    35  	if err != nil {
    36  		return err
    37  	} else if len(raw) < 2 {
    38  		return nil
    39  	}
    40  
    41  	chID, ok := raw[0].(float64)
    42  	if !ok {
    43  		return fmt.Errorf("expected message to start with a channel id but got %#v instead", raw[0])
    44  	}
    45  
    46  	chanID := int64(chID)
    47  	sub, err := c.subscriptions.lookupBySocketChannelID(chanID, socketId)
    48  	if err != nil {
    49  		// no subscribed channel for message
    50  		return err
    51  	}
    52  	c.subscriptions.heartbeat(chanID)
    53  	if sub.Public {
    54  		switch data := raw[1].(type) {
    55  		case string:
    56  			switch data {
    57  			case "hb":
    58  				// no-op, already updated heartbeat timeout from this event
    59  				return nil
    60  			case "cs":
    61  				if checksum, ok := raw[2].(float64); ok {
    62  					return c.handleChecksumChannel(sub, int(checksum))
    63  				} else {
    64  					c.log.Error("Unable to parse checksum")
    65  				}
    66  			default:
    67  				body := raw[2].([]interface{})
    68  				return c.handlePublicChannel(sub, sub.Request.Channel, data, body, msg)
    69  			}
    70  		case []interface{}:
    71  			return c.handlePublicChannel(sub, sub.Request.Channel, "", data, msg)
    72  		}
    73  	} else {
    74  		return c.handlePrivateChannel(raw)
    75  	}
    76  	return nil
    77  }
    78  
    79  func (c *Client) handleChecksumChannel(sub *subscription, checksum int) error {
    80  	symbol := sub.Request.Symbol
    81  	// force to signed integer
    82  	bChecksum := uint32(checksum)
    83  	var orderbook *Orderbook
    84  	c.mtx.Lock()
    85  	if ob, ok := c.orderbooks[symbol]; ok {
    86  		orderbook = ob
    87  	}
    88  	c.mtx.Unlock()
    89  	if orderbook != nil {
    90  		oChecksum := orderbook.Checksum()
    91  		// compare bitfinex checksum with local checksum
    92  		if bChecksum == oChecksum {
    93  			c.log.Debugf("Orderbook '%s' checksum verification successful.", symbol)
    94  		} else {
    95  			c.log.Warningf("Orderbook '%s' checksum is invalid got %d bot got %d. Data Out of sync, reconnecting.",
    96  				symbol, bChecksum, oChecksum)
    97  			err := c.sendUnsubscribeMessage(context.Background(), sub)
    98  			if err != nil {
    99  				return err
   100  			}
   101  			newSub := &SubscriptionRequest{
   102  				SubID:   c.nonce.GetNonce(), // generate new subID
   103  				Event:   sub.Request.Event,
   104  				Channel: sub.Request.Channel,
   105  				Symbol:  sub.Request.Symbol,
   106  			}
   107  			_, err_sub := c.Subscribe(context.Background(), newSub)
   108  			if err_sub != nil {
   109  				c.log.Warningf("could not resubscribe: %s", err_sub.Error())
   110  				return err_sub
   111  			}
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  func (c *Client) handlePublicChannel(sub *subscription, channel, objType string, data []interface{}, raw_msg []byte) error {
   118  	// unauthenticated data slice
   119  	// public data is returned as raw interface arrays, use a factory to convert to raw type & publish
   120  	if factory, ok := c.factories[channel]; ok {
   121  		// convert to type array of interfaces
   122  		if len(data) > 0 {
   123  			if _, ok := data[0].([]interface{}); ok {
   124  				interfaceArray := convert.ToInterfaceArray(data)
   125  				// snapshot item
   126  				c.mtx.Lock()
   127  				// lock mutex since its mutates client struct
   128  				msg, err := factory.BuildSnapshot(sub, interfaceArray, raw_msg)
   129  				c.mtx.Unlock()
   130  				if err != nil {
   131  					return err
   132  				}
   133  				if msg != nil {
   134  					c.listener <- msg
   135  				}
   136  			} else {
   137  				// single item
   138  				msg, err := factory.Build(sub, objType, data, raw_msg)
   139  				if err != nil {
   140  					return err
   141  				}
   142  				if msg != nil {
   143  					c.listener <- msg
   144  				}
   145  			}
   146  		}
   147  	} else {
   148  		// factory lookup error
   149  		return fmt.Errorf("could not find public factory for %s channel", channel)
   150  	}
   151  	return nil
   152  }
   153  
   154  func (c *Client) handlePrivateChannel(raw []interface{}) error {
   155  	// authenticated data slice, or a heartbeat
   156  	if val, ok := raw[1].(string); ok && val == "hb" {
   157  		chanID, ok := raw[0].(float64)
   158  		if !ok {
   159  			c.log.Warningf("could not find chanID: %#v", raw)
   160  			return nil
   161  		}
   162  		c.handleHeartbeat(int64(chanID))
   163  	} else {
   164  		// raw[2] is data slice
   165  		// authenticated snapshots?
   166  		if len(raw) > 2 {
   167  			if arr, ok := raw[2].([]interface{}); ok {
   168  				obj, err := c.handlePrivateDataMessage(raw[1].(string), arr)
   169  				if err != nil {
   170  					return err
   171  				}
   172  				// private data is returned as strongly typed data, publish directly
   173  				if obj != nil {
   174  					c.listener <- obj
   175  				}
   176  			}
   177  		}
   178  	}
   179  	return nil
   180  }
   181  
   182  func (c *Client) handleHeartbeat(chanID int64) {
   183  	c.subscriptions.heartbeat(chanID)
   184  }
   185  
   186  type unsubscribeMsg struct {
   187  	Event  string `json:"event"`
   188  	ChanID int64  `json:"chanId"`
   189  }
   190  
   191  // public msg: [ChanID, [Data]]
   192  // hb (both): [ChanID, "hb"]
   193  // private update msg: [ChanID, "type", [Data]]
   194  // private snapshot msg: [ChanID, "type", [[Data]]]
   195  func (c *Client) handlePrivateDataMessage(term string, data []interface{}) (ms interface{}, err error) {
   196  	if len(data) == 0 {
   197  		// empty data msg
   198  		return nil, nil
   199  	}
   200  
   201  	if term == "hb" { // Heartbeat
   202  		// TODO: Consider adding a switch to enable/disable passing these along.
   203  		return &Heartbeat{}, nil
   204  	}
   205  	/*
   206  		list, ok := data[2].([]interface{})
   207  		if !ok {
   208  			return ms, fmt.Errorf("expected data list in third position but got %#v in %#v", data[2], data)
   209  		}
   210  	*/
   211  	ms = c.convertRaw(term, data)
   212  
   213  	return
   214  }
   215  
   216  // convertRaw takes a term and the raw data attached to it to try and convert that
   217  // untyped list into a proper type.
   218  func (c *Client) convertRaw(term string, raw []interface{}) interface{} {
   219  	// The things you do to get proper types.
   220  	switch term {
   221  	case "bu":
   222  		o, err := balanceinfo.FromRaw(raw)
   223  		if err != nil {
   224  			return err
   225  		}
   226  		bu := balanceinfo.Update(*o)
   227  		return &bu
   228  	case "ps":
   229  		o, err := position.SnapshotFromRaw(raw)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		return o
   234  	case "pn":
   235  		o, err := position.FromRaw(raw)
   236  		if err != nil {
   237  			return err
   238  		}
   239  		pn := position.New(*o)
   240  		return &pn
   241  	case "pu":
   242  		o, err := position.FromRaw(raw)
   243  		if err != nil {
   244  			return err
   245  		}
   246  		pu := position.Update(*o)
   247  		return &pu
   248  	case "pc":
   249  		o, err := position.FromRaw(raw)
   250  		if err != nil {
   251  			return err
   252  		}
   253  		pc := position.Cancel(*o)
   254  		return &pc
   255  	case "ws":
   256  		o, err := wallet.SnapshotFromRaw(raw)
   257  		if err != nil {
   258  			return err
   259  		}
   260  		return o
   261  	case "wu":
   262  		o, err := wallet.FromRaw(raw)
   263  		if err != nil {
   264  			return err
   265  		}
   266  		wu := wallet.Update(*o)
   267  		return &wu
   268  	case "os":
   269  		o, err := order.SnapshotFromRaw(raw)
   270  		if err != nil {
   271  			return err
   272  		}
   273  		return o
   274  	case "on":
   275  		o, err := order.FromRaw(raw)
   276  		if err != nil {
   277  			return err
   278  		}
   279  		on := order.New(*o)
   280  		return &on
   281  	case "ou":
   282  		o, err := order.FromRaw(raw)
   283  		if err != nil {
   284  			return err
   285  		}
   286  		ou := order.Update(*o)
   287  		return &ou
   288  	case "oc":
   289  		o, err := order.FromRaw(raw)
   290  		if err != nil {
   291  			return err
   292  		}
   293  		oc := order.Cancel(*o)
   294  		return &oc
   295  	case "hts":
   296  		tu, err := tradeexecutionupdate.SnapshotFromRaw(raw)
   297  		if err != nil {
   298  			return err
   299  		}
   300  		hts := tradeexecutionupdate.HistoricalTradeSnapshot(*tu)
   301  		return &hts
   302  	case "te":
   303  		o, err := tradeexecution.FromRaw(raw)
   304  		if err != nil {
   305  			return err
   306  		}
   307  		return o
   308  	case "tu":
   309  		tu, err := tradeexecutionupdate.FromRaw(raw)
   310  		if err != nil {
   311  			return err
   312  		}
   313  		return tu
   314  	case "fte":
   315  		o, err := fundingtrade.FromRaw(raw)
   316  		if err != nil {
   317  			return err
   318  		}
   319  		fte := fundingtrade.Execution(*o)
   320  		return &fte
   321  	case "ftu":
   322  		o, err := fundingtrade.FromRaw(raw)
   323  		if err != nil {
   324  			return err
   325  		}
   326  		ftu := fundingtrade.Update(*o)
   327  		return &ftu
   328  	case "hfts":
   329  		fts, err := fundingtrade.SnapshotFromRaw(raw)
   330  		if err != nil {
   331  			return err
   332  		}
   333  		nfts := fundingtrade.HistoricalSnapshot(*fts)
   334  		return &nfts
   335  	case "n":
   336  		o, err := notification.FromRaw(raw)
   337  		if err != nil {
   338  			return err
   339  		}
   340  		return o
   341  	case "fos":
   342  		o, err := fundingoffer.SnapshotFromRaw(raw)
   343  		if err != nil {
   344  			return err
   345  		}
   346  		return o
   347  	case "fon":
   348  		o, err := fundingoffer.FromRaw(raw)
   349  		if err != nil {
   350  			return err
   351  		}
   352  		fon := fundingoffer.New(*o)
   353  		return &fon
   354  	case "fou":
   355  		o, err := fundingoffer.FromRaw(raw)
   356  		if err != nil {
   357  			return err
   358  		}
   359  		fou := fundingoffer.Update(*o)
   360  		return &fou
   361  	case "foc":
   362  		o, err := fundingoffer.FromRaw(raw)
   363  		if err != nil {
   364  			return err
   365  		}
   366  		foc := fundingoffer.Cancel(*o)
   367  		return &foc
   368  	case "fiu":
   369  		o, err := fundinginfo.FromRaw(raw)
   370  		if err != nil {
   371  			return err
   372  		}
   373  		return o
   374  	case "fcs":
   375  		o, err := fundingcredit.SnapshotFromRaw(raw)
   376  		if err != nil {
   377  			return err
   378  		}
   379  		return o
   380  	case "fcn":
   381  		o, err := fundingcredit.FromRaw(raw)
   382  		if err != nil {
   383  			return err
   384  		}
   385  		fcn := fundingcredit.New(*o)
   386  		return &fcn
   387  	case "fcu":
   388  		o, err := fundingcredit.FromRaw(raw)
   389  		if err != nil {
   390  			return err
   391  		}
   392  		fcu := fundingcredit.Update(*o)
   393  		return &fcu
   394  	case "fcc":
   395  		o, err := fundingcredit.FromRaw(raw)
   396  		if err != nil {
   397  			return err
   398  		}
   399  		fcc := fundingcredit.Cancel(*o)
   400  		return &fcc
   401  	case "fls":
   402  		o, err := fundingloan.SnapshotFromRaw(raw)
   403  		if err != nil {
   404  			return err
   405  		}
   406  		return o
   407  	case "fln":
   408  		o, err := fundingloan.FromRaw(raw)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		fln := fundingloan.New(*o)
   413  		return &fln
   414  	case "flu":
   415  		o, err := fundingloan.FromRaw(raw)
   416  		if err != nil {
   417  			return err
   418  		}
   419  		flu := fundingloan.Update(*o)
   420  		return &flu
   421  	case "flc":
   422  		o, err := fundingloan.FromRaw(raw)
   423  		if err != nil {
   424  			return err
   425  		}
   426  		flc := fundingloan.Cancel(*o)
   427  		return &flc
   428  	//case "uac":
   429  	case "hb":
   430  		return &Heartbeat{}
   431  	case "ats":
   432  		// TODO: Is not in documentation, so figure out what it is.
   433  		return nil
   434  	case "oc-req":
   435  		// TODO
   436  		return nil
   437  	case "on-req":
   438  		// TODO
   439  		return nil
   440  	case "mis": // Should not be sent anymore as of 2017-04-01
   441  		return nil
   442  	case "miu":
   443  		o, err := margin.FromRaw(raw)
   444  		if err != nil {
   445  			return err
   446  		}
   447  		// return a strongly typed reference, rather than dereference a generic interface
   448  		// too bad golang doesn't inherit an interface's underlying type when creating a reference to the interface
   449  		if base, ok := o.(*margin.InfoBase); ok {
   450  			return base
   451  		}
   452  		if update, ok := o.(*margin.InfoUpdate); ok {
   453  			return update
   454  		}
   455  		return o // better than nothing
   456  	default:
   457  		c.log.Warningf("unhandled channel data, term: %s", term)
   458  	}
   459  
   460  	return fmt.Errorf("term %q not recognized", term)
   461  }