github.com/igggame/nebulas-go@v2.1.0+incompatible/cmd/console/jsbridge.go (about)

     1  // Copyright (C) 2017 go-nebulas authors
     2  //
     3  // This file is part of the go-nebulas library.
     4  //
     5  // the go-nebulas library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // the go-nebulas library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with the go-nebulas library.  If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  
    19  package console
    20  
    21  import (
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"strings"
    26  
    27  	"bytes"
    28  	"io/ioutil"
    29  	"net/http"
    30  
    31  	"github.com/nebulasio/go-nebulas/neblet/pb"
    32  	"github.com/robertkrimen/otto"
    33  )
    34  
    35  const (
    36  	//APIVersion rpc http version
    37  	APIVersion = "v1"
    38  )
    39  
    40  type jsBridge struct {
    41  
    42  	// js request host
    43  	host string
    44  
    45  	// terminal input prompter
    46  	prompter UserPrompter
    47  
    48  	writer io.Writer
    49  }
    50  
    51  // newBirdge create a new jsbridge with given prompter and writer
    52  func newBirdge(config *nebletpb.Config, prompter UserPrompter, writer io.Writer) *jsBridge {
    53  	bridge := &jsBridge{prompter: prompter, writer: writer}
    54  	if config.GetRpc() != nil {
    55  		bridge.host = config.GetRpc().HttpListen[0]
    56  		if !strings.HasPrefix(bridge.host, "http") {
    57  			bridge.host = "http://" + bridge.host
    58  		}
    59  	} else {
    60  		bridge.host = "http://localhost:8685"
    61  	}
    62  	return bridge
    63  }
    64  
    65  // output handle the error & log in js runtime
    66  func (b *jsBridge) output(call otto.FunctionCall) {
    67  	output := []string{}
    68  	for _, argument := range call.ArgumentList {
    69  		output = append(output, fmt.Sprintf("%v", argument))
    70  	}
    71  	fmt.Fprintln(b.writer, strings.Join(output, " "))
    72  }
    73  
    74  // setHost update repl request host
    75  func (b *jsBridge) setHost(call otto.FunctionCall) otto.Value {
    76  	host := call.Argument(0)
    77  	if !host.IsString() {
    78  		return jsError(call.Otto, errors.New("setHost host is null"))
    79  	}
    80  	b.host = host.String()
    81  	return otto.NullValue()
    82  }
    83  
    84  // request handle http request
    85  func (b *jsBridge) request(call otto.FunctionCall) otto.Value {
    86  	method := call.Argument(0)
    87  	api := call.Argument(1)
    88  	if method.IsNull() || api.IsNull() {
    89  		return jsError(call.Otto, errors.New("request method/api is null"))
    90  	}
    91  
    92  	// convert args to string
    93  	JSON, _ := call.Otto.Object("JSON")
    94  	args := ""
    95  	if !call.Argument(2).IsNull() {
    96  		argsVal, err := JSON.Call("stringify", call.Argument(2))
    97  		if err != nil {
    98  			return jsError(call.Otto, err)
    99  		}
   100  		if argsVal.IsString() {
   101  			args = argsVal.String()
   102  		}
   103  	}
   104  
   105  	url := b.host + "/" + APIVersion + api.String()
   106  	//fmt.Fprintln(b.writer, "request", url, method.String(), args)
   107  	// method only support upper case.
   108  	req, err := http.NewRequest(strings.ToUpper(method.String()), url, bytes.NewBuffer([]byte(args)))
   109  	if err != nil {
   110  		return jsError(call.Otto, err)
   111  	}
   112  	req.Header.Set("Content-Type", "application/json")
   113  
   114  	client := &http.Client{}
   115  	resp, err := client.Do(req)
   116  	if err != nil {
   117  		return jsError(call.Otto, err)
   118  	}
   119  
   120  	defer resp.Body.Close()
   121  	result, err := ioutil.ReadAll(resp.Body)
   122  	if err != nil {
   123  		return jsError(call.Otto, err)
   124  	}
   125  	//fmt.Fprintln(b.writer, "result:", result)
   126  	response, err := JSON.Call("parse", string(result))
   127  	if err != nil {
   128  		// if result can't be parse to json obj ,return origin string
   129  		response, _ = otto.ToValue(string(result))
   130  	}
   131  
   132  	if fn := call.Argument(3); fn.Class() == "Function" {
   133  		fn.Call(otto.NullValue(), response)
   134  		return otto.UndefinedValue()
   135  	}
   136  	return response
   137  }
   138  
   139  // newAccount handle the account generate with passphrase input
   140  func (b *jsBridge) newAccount(call otto.FunctionCall) otto.Value {
   141  	var (
   142  		password string
   143  		err      error
   144  	)
   145  	switch {
   146  	// No password was specified, prompt the user for it
   147  	case len(call.ArgumentList) == 0:
   148  		if password, err = b.prompter.PromptPassphrase("Passphrase: "); err != nil {
   149  			fmt.Fprintln(b.writer, err)
   150  			return otto.NullValue()
   151  		}
   152  		var confirm string
   153  		if confirm, err = b.prompter.PromptPassphrase("Repeat passphrase: "); err != nil {
   154  			fmt.Fprintln(b.writer, err)
   155  			return otto.NullValue()
   156  		}
   157  		if password != confirm {
   158  			fmt.Fprintln(b.writer, errors.New("passphrase don't match"))
   159  			return otto.NullValue()
   160  		}
   161  	case len(call.ArgumentList) == 1 && call.Argument(0).IsString():
   162  		password, _ = call.Argument(0).ToString()
   163  	default:
   164  		fmt.Fprintln(b.writer, errors.New("unexpected argument count"))
   165  		return otto.NullValue()
   166  	}
   167  	ret, err := call.Otto.Call("bridge.newAccount", nil, password)
   168  	if err != nil {
   169  		fmt.Fprintln(b.writer, err)
   170  		return otto.NullValue()
   171  	}
   172  	return ret
   173  }
   174  
   175  // signTransaction handle the account unlock with passphrase input
   176  func (b *jsBridge) unlockAccount(call otto.FunctionCall) otto.Value {
   177  	if !call.Argument(0).IsString() {
   178  		fmt.Fprintln(b.writer, errors.New("address arg must be string"))
   179  		return otto.NullValue()
   180  	}
   181  	address := call.Argument(0)
   182  
   183  	var passphrase otto.Value
   184  
   185  	if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
   186  		fmt.Fprintf(b.writer, "Unlock account %s\n", address)
   187  		var (
   188  			input string
   189  			err   error
   190  		)
   191  		if input, err = b.prompter.PromptPassphrase("Passphrase: "); err != nil {
   192  			fmt.Fprintln(b.writer, err)
   193  			return otto.NullValue()
   194  		}
   195  		passphrase, _ = otto.ToValue(input)
   196  	} else {
   197  		if !call.Argument(1).IsString() {
   198  			fmt.Fprintln(b.writer, errors.New("password must be a string"))
   199  			return otto.NullValue()
   200  		}
   201  		passphrase = call.Argument(1)
   202  	}
   203  
   204  	// Send the request to the backend and return
   205  	val, err := call.Otto.Call("bridge.unlockAccount", nil, address, passphrase)
   206  	if err != nil {
   207  		fmt.Fprintln(b.writer, err)
   208  		return otto.NullValue()
   209  	}
   210  	return val
   211  }
   212  
   213  // sendTransactionWithPassphrase handle the transaction send with passphrase input
   214  func (b *jsBridge) sendTransactionWithPassphrase(call otto.FunctionCall) otto.Value {
   215  	if !call.Argument(0).IsString() || !call.Argument(1).IsString() {
   216  		fmt.Fprintln(b.writer, errors.New("from/to address arg must be string"))
   217  		return otto.NullValue()
   218  	}
   219  	var passphrase otto.Value
   220  	if call.Argument(8).IsUndefined() || call.Argument(8).IsNull() {
   221  		var (
   222  			input string
   223  			err   error
   224  		)
   225  		if input, err = b.prompter.PromptPassphrase("Passphrase: "); err != nil {
   226  			fmt.Fprintln(b.writer, err)
   227  			return otto.NullValue()
   228  		}
   229  		passphrase, _ = otto.ToValue(input)
   230  	} else {
   231  		if !call.Argument(8).IsString() {
   232  			fmt.Fprintln(b.writer, errors.New("password must be a string"))
   233  			return otto.NullValue()
   234  		}
   235  		passphrase = call.Argument(1)
   236  	}
   237  	// Send the request to the backend and return
   238  	val, err := call.Otto.Call("bridge.sendTransactionWithPassphrase", nil,
   239  		call.Argument(0), call.Argument(1), call.Argument(2),
   240  		call.Argument(3), call.Argument(4), call.Argument(5),
   241  		call.Argument(6), call.Argument(7), passphrase)
   242  	if err != nil {
   243  		fmt.Fprintln(b.writer, err)
   244  		return otto.NullValue()
   245  	}
   246  	return val
   247  }
   248  
   249  // signTransactionWithPassphrase handle the transaction sign with passphrase input
   250  func (b *jsBridge) signTransactionWithPassphrase(call otto.FunctionCall) otto.Value {
   251  	if !call.Argument(0).IsString() || !call.Argument(1).IsString() {
   252  		fmt.Fprintln(b.writer, errors.New("from/to address arg must be string"))
   253  		return otto.NullValue()
   254  	}
   255  	var passphrase otto.Value
   256  	if call.Argument(8).IsUndefined() || call.Argument(8).IsNull() {
   257  		var (
   258  			input string
   259  			err   error
   260  		)
   261  		if input, err = b.prompter.PromptPassphrase("Passphrase: "); err != nil {
   262  			fmt.Fprintln(b.writer, err)
   263  			return otto.NullValue()
   264  		}
   265  		passphrase, _ = otto.ToValue(input)
   266  	} else {
   267  		if !call.Argument(8).IsString() {
   268  			fmt.Fprintln(b.writer, errors.New("password must be a string"))
   269  			return otto.NullValue()
   270  		}
   271  		passphrase = call.Argument(1)
   272  	}
   273  	// Send the request to the backend and return
   274  	val, err := call.Otto.Call("bridge.signTransactionWithPassphrase", nil,
   275  		call.Argument(0), call.Argument(1), call.Argument(2),
   276  		call.Argument(3), call.Argument(4), call.Argument(5),
   277  		call.Argument(6), call.Argument(7), passphrase)
   278  	if err != nil {
   279  		fmt.Fprintln(b.writer, err)
   280  		return otto.NullValue()
   281  	}
   282  	return val
   283  }
   284  
   285  func jsError(otto *otto.Otto, err error) otto.Value {
   286  	resp, _ := otto.Object(`({})`)
   287  	resp.Set("error", map[string]interface{}{"code": -1, "message": err.Error()})
   288  	return resp.Value()
   289  }