mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
Combine our HTTP RPC and Websocket services into a single HTTP server, eliminating the --ws-port flag to swapd and using --swapd-port flag for swapcli.
405 lines
9.2 KiB
Go
405 lines
9.2 KiB
Go
package wsclient
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/athanorlabs/atomic-swap/common/rpctypes"
|
|
"github.com/athanorlabs/atomic-swap/common/types"
|
|
|
|
"github.com/gorilla/websocket"
|
|
logging "github.com/ipfs/go-log"
|
|
)
|
|
|
|
var log = logging.Logger("rpcclient")
|
|
|
|
// WsClient ...
|
|
type WsClient interface {
|
|
Close()
|
|
Discover(provides types.ProvidesCoin, searchTime uint64) ([][]string, error)
|
|
Query(maddr string) (*rpctypes.QueryPeerResponse, error)
|
|
SubscribeSwapStatus(id types.Hash) (<-chan types.Status, error)
|
|
TakeOfferAndSubscribe(multiaddr, offerID string, providesAmount float64) (ch <-chan types.Status, err error)
|
|
MakeOfferAndSubscribe(min, max float64,
|
|
exchangeRate types.ExchangeRate, ethAsset types.EthAsset) (string, <-chan types.Status, error)
|
|
}
|
|
|
|
type wsClient struct {
|
|
wmu sync.Mutex
|
|
rmu sync.Mutex
|
|
conn *websocket.Conn
|
|
}
|
|
|
|
// NewWsClient ...
|
|
func NewWsClient(ctx context.Context, endpoint string) (*wsClient, error) { ///nolint:revive
|
|
conn, resp, err := websocket.DefaultDialer.DialContext(ctx, endpoint, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to dial WS endpoint: %w", err)
|
|
}
|
|
|
|
if err = resp.Body.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &wsClient{
|
|
conn: conn,
|
|
}, nil
|
|
}
|
|
|
|
func (c *wsClient) Close() {
|
|
_ = c.conn.Close()
|
|
}
|
|
|
|
func (c *wsClient) writeJSON(msg *rpctypes.Request) error {
|
|
c.wmu.Lock()
|
|
defer c.wmu.Unlock()
|
|
return c.conn.WriteJSON(msg)
|
|
}
|
|
|
|
func (c *wsClient) read() ([]byte, error) {
|
|
c.rmu.Lock()
|
|
defer c.rmu.Unlock()
|
|
_, message, err := c.conn.ReadMessage()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return message, nil
|
|
}
|
|
|
|
func (c *wsClient) Discover(provides types.ProvidesCoin, searchTime uint64) ([][]string, error) {
|
|
params := &rpctypes.DiscoverRequest{
|
|
Provides: provides,
|
|
SearchTime: searchTime,
|
|
}
|
|
|
|
bz, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &rpctypes.Request{
|
|
JSONRPC: rpctypes.DefaultJSONRPCVersion,
|
|
Method: rpctypes.NetDiscover,
|
|
Params: bz,
|
|
ID: 0,
|
|
}
|
|
|
|
if err = c.writeJSON(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
message, err := c.read()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read websockets message: %s", err)
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var dresp *rpctypes.DiscoverResponse
|
|
if err := json.Unmarshal(resp.Result, &dresp); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
|
|
}
|
|
|
|
return dresp.Peers, nil
|
|
}
|
|
|
|
func (c *wsClient) Query(maddr string) (*rpctypes.QueryPeerResponse, error) {
|
|
params := &rpctypes.QueryPeerRequest{
|
|
Multiaddr: maddr,
|
|
}
|
|
|
|
bz, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &rpctypes.Request{
|
|
JSONRPC: rpctypes.DefaultJSONRPCVersion,
|
|
Method: rpctypes.NetQueryPeer,
|
|
Params: bz,
|
|
ID: 0,
|
|
}
|
|
|
|
if err = c.writeJSON(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// read ID from connection
|
|
message, err := c.read()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read websockets message: %s", err)
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var dresp *rpctypes.QueryPeerResponse
|
|
if err := json.Unmarshal(resp.Result, &dresp); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
|
|
}
|
|
|
|
return dresp, nil
|
|
}
|
|
|
|
// SubscribeSwapStatus returns a channel that is written to each time the swap's status updates.
|
|
// If there is no swap with the given ID, it returns an error.
|
|
func (c *wsClient) SubscribeSwapStatus(id types.Hash) (<-chan types.Status, error) {
|
|
params := &rpctypes.SubscribeSwapStatusRequest{
|
|
ID: id,
|
|
}
|
|
|
|
bz, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &rpctypes.Request{
|
|
JSONRPC: rpctypes.DefaultJSONRPCVersion,
|
|
Method: rpctypes.SubscribeSwapStatus,
|
|
Params: bz,
|
|
ID: 0,
|
|
}
|
|
|
|
if err = c.writeJSON(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
respCh := make(chan types.Status)
|
|
|
|
go func() {
|
|
defer close(respCh)
|
|
|
|
for {
|
|
message, err := c.read()
|
|
if err != nil {
|
|
log.Warnf("failed to read websockets message: %s", err)
|
|
break
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
log.Warnf("failed to unmarshal response: %s", err)
|
|
break
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
log.Warnf("websocket server returned error: %s", resp.Error)
|
|
break
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var status *rpctypes.SubscribeSwapStatusResponse
|
|
if err := json.Unmarshal(resp.Result, &status); err != nil {
|
|
log.Warnf("failed to unmarshal response: %s", err)
|
|
break
|
|
}
|
|
|
|
s := types.NewStatus(status.Status)
|
|
respCh <- s
|
|
if !s.IsOngoing() {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return respCh, nil
|
|
}
|
|
|
|
func (c *wsClient) TakeOfferAndSubscribe(multiaddr, offerID string,
|
|
providesAmount float64) (ch <-chan types.Status, err error) {
|
|
params := &rpctypes.TakeOfferRequest{
|
|
Multiaddr: multiaddr,
|
|
OfferID: offerID,
|
|
ProvidesAmount: providesAmount,
|
|
}
|
|
|
|
bz, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &rpctypes.Request{
|
|
JSONRPC: rpctypes.DefaultJSONRPCVersion,
|
|
Method: rpctypes.SubscribeTakeOffer,
|
|
Params: bz,
|
|
ID: 0,
|
|
}
|
|
|
|
if err = c.writeJSON(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// read ID from connection
|
|
message, err := c.read()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read websockets message: %s", err)
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var idResp *rpctypes.TakeOfferResponse
|
|
if err := json.Unmarshal(resp.Result, &idResp); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
|
|
}
|
|
|
|
respCh := make(chan types.Status)
|
|
|
|
go func() {
|
|
defer close(respCh)
|
|
|
|
for {
|
|
message, err := c.read()
|
|
if err != nil {
|
|
log.Warnf("failed to read websockets message: %s", err)
|
|
break
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
log.Warnf("failed to unmarshal response: %s", err)
|
|
break
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
log.Warnf("websocket server returned error: %s", resp.Error)
|
|
break
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var status *rpctypes.SubscribeSwapStatusResponse
|
|
if err := json.Unmarshal(resp.Result, &status); err != nil {
|
|
log.Warnf("failed to unmarshal swap status response: %s", err)
|
|
break
|
|
}
|
|
|
|
s := types.NewStatus(status.Status)
|
|
respCh <- s
|
|
if !s.IsOngoing() {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return respCh, nil
|
|
}
|
|
|
|
func (c *wsClient) MakeOfferAndSubscribe(min, max float64,
|
|
exchangeRate types.ExchangeRate, ethAsset types.EthAsset) (string, <-chan types.Status, error) {
|
|
params := &rpctypes.MakeOfferRequest{
|
|
MinimumAmount: min,
|
|
MaximumAmount: max,
|
|
ExchangeRate: exchangeRate,
|
|
EthAsset: ethAsset.Address().String(),
|
|
}
|
|
|
|
bz, err := json.Marshal(params)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
req := &rpctypes.Request{
|
|
JSONRPC: rpctypes.DefaultJSONRPCVersion,
|
|
Method: rpctypes.SubscribeMakeOffer,
|
|
Params: bz,
|
|
ID: 0,
|
|
}
|
|
|
|
if err = c.writeJSON(req); err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// read ID from connection
|
|
message, err := c.read()
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to read websockets message: %s", err)
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
return "", nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
|
|
}
|
|
|
|
// read synchronous response (offer ID and infofile)
|
|
var respData *rpctypes.MakeOfferResponse
|
|
if err := json.Unmarshal(resp.Result, &respData); err != nil {
|
|
return "", nil, fmt.Errorf("failed to unmarshal response: %s", err)
|
|
}
|
|
|
|
respCh := make(chan types.Status)
|
|
|
|
go func() {
|
|
defer close(respCh)
|
|
|
|
for {
|
|
message, err := c.read()
|
|
if err != nil {
|
|
log.Warnf("failed to read websockets message: %s", err)
|
|
break
|
|
}
|
|
|
|
var resp *rpctypes.Response
|
|
err = json.Unmarshal(message, &resp)
|
|
if err != nil {
|
|
log.Warnf("failed to unmarshal response: %s", err)
|
|
break
|
|
}
|
|
|
|
if resp.Error != nil {
|
|
log.Warnf("websocket server returned error: %s", resp.Error)
|
|
break
|
|
}
|
|
|
|
log.Debugf("received message over websockets: %s", message)
|
|
var status *rpctypes.SubscribeSwapStatusResponse
|
|
if err := json.Unmarshal(resp.Result, &status); err != nil {
|
|
log.Warnf("failed to unmarshal response: %s", err)
|
|
break
|
|
}
|
|
|
|
s := types.NewStatus(status.Status)
|
|
respCh <- s
|
|
if !s.IsOngoing() {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return respData.ID, respCh, nil
|
|
}
|