decred.org/dcrdex@v1.0.5/tatanka/tcp/client/client.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package client
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/url"
    10  	"sync"
    11  	"time"
    12  
    13  	"decred.org/dcrdex/client/comms"
    14  	"decred.org/dcrdex/dex"
    15  	"decred.org/dcrdex/dex/msgjson"
    16  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    17  )
    18  
    19  type Client struct {
    20  	ctx    context.Context
    21  	log    dex.Logger
    22  	url    *url.URL
    23  	cert   []byte
    24  	cl     comms.WsConn
    25  	cm     *dex.ConnectionMaster
    26  	handle func(*msgjson.Message) *msgjson.Error
    27  }
    28  
    29  type Config struct {
    30  	Logger        dex.Logger
    31  	URL           string
    32  	Cert          []byte
    33  	PrivateKey    *secp256k1.PrivateKey
    34  	HandleMessage func(*msgjson.Message) *msgjson.Error
    35  }
    36  
    37  func New(cfg *Config) (*Client, error) {
    38  	u, err := url.Parse(cfg.URL)
    39  	if err != nil {
    40  		return nil, fmt.Errorf("error parsing URL: %w", err)
    41  	}
    42  	switch u.Scheme {
    43  	case "ws", "wss":
    44  	default:
    45  		return nil, fmt.Errorf("protocol should be 'ws' or 'wss', not %q", u.Scheme)
    46  	}
    47  	return &Client{
    48  		log:    cfg.Logger,
    49  		url:    u,
    50  		cert:   cfg.Cert,
    51  		handle: cfg.HandleMessage,
    52  	}, nil
    53  }
    54  
    55  func (c *Client) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) {
    56  	c.ctx = ctx
    57  	if c.cl, err = comms.NewWsConn(&comms.WsCfg{
    58  		URL:      c.url.String(),
    59  		PingWait: 20 * time.Second,
    60  		Cert:     c.cert,
    61  		ReconnectSync: func() {
    62  			fmt.Println("## RECONNECTED RECONNECTED RECONNECTED RECONNECTED ")
    63  		},
    64  		ConnectEventFunc: func(status comms.ConnectionStatus) {
    65  			if status == comms.Disconnected {
    66  				// Remove it from the map.
    67  				c.log.Infof("WebSockets client for %s has disconnected", c.url)
    68  			}
    69  		},
    70  		Logger: c.log.SubLogger("TC"),
    71  	}); err != nil {
    72  		return nil, fmt.Errorf("error creating websockets connection: %w", err)
    73  	}
    74  
    75  	var wg sync.WaitGroup
    76  
    77  	msgs := c.cl.MessageSource()
    78  
    79  	c.cm = dex.NewConnectionMaster(c.cl)
    80  	if err := c.cm.ConnectOnce(ctx); err != nil {
    81  		return nil, fmt.Errorf("error connecting to %q: %w", c.url, err)
    82  	}
    83  
    84  	wg.Add(1)
    85  	go func() {
    86  		defer wg.Done()
    87  		for {
    88  			select {
    89  			case msg := <-msgs:
    90  				c.handle(msg)
    91  			case <-ctx.Done():
    92  				return
    93  			}
    94  		}
    95  	}()
    96  
    97  	return &wg, nil
    98  }
    99  
   100  func (c *Client) Send(msg *msgjson.Message) error {
   101  	return c.cl.Send(msg)
   102  }
   103  
   104  func (c *Client) Request(msg *msgjson.Message, respHandler func(*msgjson.Message)) error {
   105  	return c.cl.Request(msg, respHandler)
   106  }