github.com/status-im/status-go@v1.1.0/services/connector/commands/client_handler.go (about)

     1  package commands
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/status-im/status-go/eth-node/types"
    12  	"github.com/status-im/status-go/signal"
    13  	"github.com/status-im/status-go/transactions"
    14  )
    15  
    16  var (
    17  	WalletResponseMaxInterval = 20 * time.Minute
    18  
    19  	ErrWalletResponseTimeout                  = fmt.Errorf("timeout waiting for wallet response")
    20  	ErrEmptyAccountsShared                    = fmt.Errorf("empty accounts were shared by wallet")
    21  	ErrRequestAccountsRejectedByUser          = fmt.Errorf("request accounts was rejected by user")
    22  	ErrSendTransactionRejectedByUser          = fmt.Errorf("send transaction was rejected by user")
    23  	ErrPersonalSignRejectedByUser             = fmt.Errorf("personal sign was rejected by user")
    24  	ErrEmptyRequestID                         = fmt.Errorf("empty requestID")
    25  	ErrAnotherConnectorOperationIsAwaitingFor = fmt.Errorf("another connector operation is awaiting for user input")
    26  )
    27  
    28  type MessageType int
    29  
    30  const (
    31  	RequestAccountsAccepted MessageType = iota
    32  	SendTransactionAccepted
    33  	PersonalSignAccepted
    34  	Rejected
    35  )
    36  
    37  type Message struct {
    38  	Type MessageType
    39  	Data interface{}
    40  }
    41  
    42  type ClientSideHandler struct {
    43  	responseChannel  chan Message
    44  	isRequestRunning int32
    45  }
    46  
    47  func NewClientSideHandler() *ClientSideHandler {
    48  	return &ClientSideHandler{
    49  		responseChannel:  make(chan Message, 1), // Buffer of 1 to avoid blocking
    50  		isRequestRunning: 0,
    51  	}
    52  }
    53  
    54  func (c *ClientSideHandler) generateRequestID(dApp signal.ConnectorDApp) string {
    55  	rawID := fmt.Sprintf("%d%s", time.Now().UnixMilli(), dApp.URL)
    56  	hash := sha256.Sum256([]byte(rawID))
    57  	return hex.EncodeToString(hash[:])
    58  }
    59  
    60  func (c *ClientSideHandler) setRequestRunning() bool {
    61  	return atomic.CompareAndSwapInt32(&c.isRequestRunning, 0, 1)
    62  }
    63  
    64  func (c *ClientSideHandler) clearRequestRunning() {
    65  	atomic.StoreInt32(&c.isRequestRunning, 0)
    66  }
    67  
    68  func (c *ClientSideHandler) RequestShareAccountForDApp(dApp signal.ConnectorDApp) (types.Address, uint64, error) {
    69  	if !c.setRequestRunning() {
    70  		return types.Address{}, 0, ErrAnotherConnectorOperationIsAwaitingFor
    71  	}
    72  	defer c.clearRequestRunning()
    73  
    74  	requestID := c.generateRequestID(dApp)
    75  	signal.SendConnectorSendRequestAccounts(dApp, requestID)
    76  
    77  	timeout := time.After(WalletResponseMaxInterval)
    78  
    79  	for {
    80  		select {
    81  		case msg := <-c.responseChannel:
    82  			switch msg.Type {
    83  			case RequestAccountsAccepted:
    84  				response := msg.Data.(RequestAccountsAcceptedArgs)
    85  				if response.RequestID == requestID {
    86  					return response.Account, response.ChainID, nil
    87  				}
    88  			case Rejected:
    89  				response := msg.Data.(RejectedArgs)
    90  				if response.RequestID == requestID {
    91  					return types.Address{}, 0, ErrRequestAccountsRejectedByUser
    92  				}
    93  			}
    94  		case <-timeout:
    95  			return types.Address{}, 0, ErrWalletResponseTimeout
    96  		}
    97  	}
    98  }
    99  
   100  func (c *ClientSideHandler) RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error {
   101  	if args.RequestID == "" {
   102  		return ErrEmptyRequestID
   103  	}
   104  
   105  	c.responseChannel <- Message{Type: RequestAccountsAccepted, Data: args}
   106  	return nil
   107  }
   108  
   109  func (c *ClientSideHandler) RequestAccountsRejected(args RejectedArgs) error {
   110  	if args.RequestID == "" {
   111  		return ErrEmptyRequestID
   112  	}
   113  
   114  	c.responseChannel <- Message{Type: Rejected, Data: args}
   115  	return nil
   116  }
   117  
   118  func (c *ClientSideHandler) RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error) {
   119  	if !c.setRequestRunning() {
   120  		return types.Hash{}, ErrAnotherConnectorOperationIsAwaitingFor
   121  	}
   122  	defer c.clearRequestRunning()
   123  
   124  	txArgsJson, err := json.Marshal(txArgs)
   125  	if err != nil {
   126  		return types.Hash{}, fmt.Errorf("failed to marshal txArgs: %v", err)
   127  	}
   128  
   129  	requestID := c.generateRequestID(dApp)
   130  	signal.SendConnectorSendTransaction(dApp, chainID, string(txArgsJson), requestID)
   131  
   132  	timeout := time.After(WalletResponseMaxInterval)
   133  
   134  	for {
   135  		select {
   136  		case msg := <-c.responseChannel:
   137  			switch msg.Type {
   138  			case SendTransactionAccepted:
   139  				response := msg.Data.(SendTransactionAcceptedArgs)
   140  				if response.RequestID == requestID {
   141  					return response.Hash, nil
   142  				}
   143  			case Rejected:
   144  				response := msg.Data.(RejectedArgs)
   145  				if response.RequestID == requestID {
   146  					return types.Hash{}, ErrSendTransactionRejectedByUser
   147  				}
   148  			}
   149  		case <-timeout:
   150  			return types.Hash{}, ErrWalletResponseTimeout
   151  		}
   152  	}
   153  }
   154  
   155  func (c *ClientSideHandler) SendTransactionAccepted(args SendTransactionAcceptedArgs) error {
   156  	if args.RequestID == "" {
   157  		return ErrEmptyRequestID
   158  	}
   159  
   160  	c.responseChannel <- Message{Type: SendTransactionAccepted, Data: args}
   161  	return nil
   162  }
   163  
   164  func (c *ClientSideHandler) SendTransactionRejected(args RejectedArgs) error {
   165  	if args.RequestID == "" {
   166  		return ErrEmptyRequestID
   167  	}
   168  
   169  	c.responseChannel <- Message{Type: Rejected, Data: args}
   170  	return nil
   171  }
   172  
   173  func (c *ClientSideHandler) RequestPersonalSign(dApp signal.ConnectorDApp, challenge, address string) (string, error) {
   174  	if !c.setRequestRunning() {
   175  		return "", ErrAnotherConnectorOperationIsAwaitingFor
   176  	}
   177  	defer c.clearRequestRunning()
   178  
   179  	requestID := c.generateRequestID(dApp)
   180  	signal.SendConnectorPersonalSign(dApp, requestID, challenge, address)
   181  
   182  	timeout := time.After(WalletResponseMaxInterval)
   183  
   184  	for {
   185  		select {
   186  		case msg := <-c.responseChannel:
   187  			switch msg.Type {
   188  			case PersonalSignAccepted:
   189  				response := msg.Data.(PersonalSignAcceptedArgs)
   190  				if response.RequestID == requestID {
   191  					return response.Signature, nil
   192  				}
   193  			case Rejected:
   194  				response := msg.Data.(RejectedArgs)
   195  				if response.RequestID == requestID {
   196  					return "", ErrPersonalSignRejectedByUser
   197  				}
   198  			}
   199  		case <-timeout:
   200  			return "", ErrWalletResponseTimeout
   201  		}
   202  	}
   203  }
   204  
   205  func (c *ClientSideHandler) PersonalSignAccepted(args PersonalSignAcceptedArgs) error {
   206  	if args.RequestID == "" {
   207  		return ErrEmptyRequestID
   208  	}
   209  
   210  	c.responseChannel <- Message{Type: PersonalSignAccepted, Data: args}
   211  	return nil
   212  }
   213  
   214  func (c *ClientSideHandler) PersonalSignRejected(args RejectedArgs) error {
   215  	if args.RequestID == "" {
   216  		return ErrEmptyRequestID
   217  	}
   218  
   219  	c.responseChannel <- Message{Type: Rejected, Data: args}
   220  	return nil
   221  }