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 }