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  }