github.com/neatio-net/neatio@v1.7.3-0.20231114194659-f4d7a2226baa/utilities/console/console.go (about)

     1  package console
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/signal"
     9  	"path/filepath"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  	"syscall"
    14  
    15  	"github.com/mattn/go-colorable"
    16  	"github.com/neatio-net/neatio/internal/jsre"
    17  	"github.com/neatio-net/neatio/internal/web3ext"
    18  	"github.com/neatio-net/neatio/network/rpc"
    19  	"github.com/peterh/liner"
    20  	"github.com/robertkrimen/otto"
    21  )
    22  
    23  var (
    24  	passwordRegexp = regexp.MustCompile(`personal.[nus]`)
    25  	onlyWhitespace = regexp.MustCompile(`^\s*$`)
    26  	exit           = regexp.MustCompile(`^\s*exit\s*;*\s*$`)
    27  )
    28  
    29  const HistoryFile = "history"
    30  
    31  const DefaultPrompt = "> "
    32  
    33  type Config struct {
    34  	DataDir  string
    35  	DocRoot  string
    36  	Client   *rpc.Client
    37  	Prompt   string
    38  	Prompter UserPrompter
    39  	Printer  io.Writer
    40  	Preload  []string
    41  }
    42  
    43  type Console struct {
    44  	client   *rpc.Client
    45  	jsre     *jsre.JSRE
    46  	prompt   string
    47  	prompter UserPrompter
    48  	histPath string
    49  	history  []string
    50  	printer  io.Writer
    51  }
    52  
    53  func New(config Config) (*Console, error) {
    54  
    55  	if config.Prompter == nil {
    56  		config.Prompter = Stdin
    57  	}
    58  	if config.Prompt == "" {
    59  		config.Prompt = DefaultPrompt
    60  	}
    61  	if config.Printer == nil {
    62  		config.Printer = colorable.NewColorableStdout()
    63  	}
    64  
    65  	console := &Console{
    66  		client:   config.Client,
    67  		jsre:     jsre.New(config.DocRoot, config.Printer),
    68  		prompt:   config.Prompt,
    69  		prompter: config.Prompter,
    70  		printer:  config.Printer,
    71  		histPath: filepath.Join(config.DataDir, HistoryFile),
    72  	}
    73  	if err := os.MkdirAll(config.DataDir, 0700); err != nil {
    74  		return nil, err
    75  	}
    76  	if err := console.init(config.Preload); err != nil {
    77  		return nil, err
    78  	}
    79  	return console, nil
    80  }
    81  
    82  func (c *Console) init(preload []string) error {
    83  
    84  	bridge := newBridge(c.client, c.prompter, c.printer)
    85  	c.jsre.Set("jeth", struct{}{})
    86  
    87  	jethObj, _ := c.jsre.Get("jeth")
    88  	jethObj.Object().Set("send", bridge.Send)
    89  	jethObj.Object().Set("sendAsync", bridge.Send)
    90  
    91  	consoleObj, _ := c.jsre.Get("console")
    92  	consoleObj.Object().Set("log", c.consoleOutput)
    93  	consoleObj.Object().Set("error", c.consoleOutput)
    94  
    95  	if err := c.jsre.Compile("bignumber.js", jsre.BigNumber_JS); err != nil {
    96  		return fmt.Errorf("bignumber.js: %v", err)
    97  	}
    98  	if err := c.jsre.Compile("web3.js", jsre.Web3_JS); err != nil {
    99  		return fmt.Errorf("web3.js: %v", err)
   100  	}
   101  	if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil {
   102  		return fmt.Errorf("web3 require: %v", err)
   103  	}
   104  	if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil {
   105  		return fmt.Errorf("web3 provider: %v", err)
   106  	}
   107  
   108  	apis, err := c.client.SupportedModules()
   109  	if err != nil {
   110  		return fmt.Errorf("api modules: %v", err)
   111  	}
   112  	flatten := "var eth = web3.eth; var personal = web3.personal; "
   113  	for api := range apis {
   114  		if api == "web3" {
   115  			continue
   116  		}
   117  		if file, ok := web3ext.Modules[api]; ok {
   118  
   119  			if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil {
   120  				return fmt.Errorf("%s.js: %v", api, err)
   121  			}
   122  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   123  		} else if obj, err := c.jsre.Run("web3." + api); err == nil && obj.IsObject() {
   124  
   125  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   126  		}
   127  	}
   128  	if _, err = c.jsre.Run(flatten); err != nil {
   129  		return fmt.Errorf("namespace flattening: %v", err)
   130  	}
   131  
   132  	if c.prompter != nil {
   133  
   134  		personal, err := c.jsre.Get("personal")
   135  		if err != nil {
   136  			return err
   137  		}
   138  
   139  		if obj := personal.Object(); obj != nil {
   140  			if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
   141  				return fmt.Errorf("personal.openWallet: %v", err)
   142  			}
   143  			if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
   144  				return fmt.Errorf("personal.unlockAccount: %v", err)
   145  			}
   146  			if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
   147  				return fmt.Errorf("personal.newAccount: %v", err)
   148  			}
   149  			if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
   150  				return fmt.Errorf("personal.sign: %v", err)
   151  			}
   152  			obj.Set("openWallet", bridge.OpenWallet)
   153  			obj.Set("unlockAccount", bridge.UnlockAccount)
   154  			obj.Set("newAccount", bridge.NewAccount)
   155  			obj.Set("sign", bridge.Sign)
   156  		}
   157  	}
   158  
   159  	admin, err := c.jsre.Get("admin")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if obj := admin.Object(); obj != nil {
   164  		obj.Set("sleepBlocks", bridge.SleepBlocks)
   165  		obj.Set("sleep", bridge.Sleep)
   166  		obj.Set("clearHistory", c.clearHistory)
   167  	}
   168  
   169  	for _, path := range preload {
   170  		if err := c.jsre.Exec(path); err != nil {
   171  			failure := err.Error()
   172  			if ottoErr, ok := err.(*otto.Error); ok {
   173  				failure = ottoErr.String()
   174  			}
   175  			return fmt.Errorf("%s: %v", path, failure)
   176  		}
   177  	}
   178  
   179  	if c.prompter != nil {
   180  		if content, err := ioutil.ReadFile(c.histPath); err != nil {
   181  			c.prompter.SetHistory(nil)
   182  		} else {
   183  			c.history = strings.Split(string(content), "\n")
   184  			c.prompter.SetHistory(c.history)
   185  		}
   186  		c.prompter.SetWordCompleter(c.AutoCompleteInput)
   187  	}
   188  	return nil
   189  }
   190  
   191  func (c *Console) clearHistory() {
   192  	c.history = nil
   193  	c.prompter.ClearHistory()
   194  	if err := os.Remove(c.histPath); err != nil {
   195  		fmt.Fprintln(c.printer, "can't delete history file:", err)
   196  	} else {
   197  		fmt.Fprintln(c.printer, "history file deleted")
   198  	}
   199  }
   200  
   201  func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
   202  	output := []string{}
   203  	for _, argument := range call.ArgumentList {
   204  		output = append(output, fmt.Sprintf("%v", argument))
   205  	}
   206  	fmt.Fprintln(c.printer, strings.Join(output, " "))
   207  	return otto.Value{}
   208  }
   209  
   210  func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) {
   211  
   212  	if len(line) == 0 || pos == 0 {
   213  		return "", nil, ""
   214  	}
   215  
   216  	start := pos - 1
   217  	for ; start > 0; start-- {
   218  
   219  		if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') {
   220  			continue
   221  		}
   222  
   223  		if start >= 3 && line[start-3:start] == "web3" {
   224  			start -= 3
   225  			continue
   226  		}
   227  
   228  		start++
   229  		break
   230  	}
   231  	return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
   232  }
   233  
   234  func (c *Console) Welcome() {
   235  
   236  	fmt.Fprintf(c.printer, "Welcome to the NEATIO JavaScript console!\n\n")
   237  	c.jsre.Run(`
   238  		console.log("instance: " + web3.version.node);
   239  		console.log("coinbase: " + eth.coinbase);
   240  		console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")");
   241  		console.log(" datadir: " + admin.datadir);
   242  	`)
   243  
   244  	if apis, err := c.client.SupportedModules(); err == nil {
   245  		modules := make([]string, 0, len(apis))
   246  		for api, version := range apis {
   247  			modules = append(modules, fmt.Sprintf("%s:%s", api, version))
   248  		}
   249  		sort.Strings(modules)
   250  		fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " "))
   251  	}
   252  	fmt.Fprintln(c.printer)
   253  }
   254  
   255  func (c *Console) Evaluate(statement string) error {
   256  	defer func() {
   257  		if r := recover(); r != nil {
   258  			fmt.Fprintf(c.printer, "[native] error: %v\n", r)
   259  		}
   260  	}()
   261  	return c.jsre.Evaluate(statement, c.printer)
   262  }
   263  
   264  func (c *Console) Interactive() {
   265  	var (
   266  		prompt    = c.prompt
   267  		indents   = 0
   268  		input     = ""
   269  		scheduler = make(chan string)
   270  	)
   271  
   272  	go func() {
   273  		for {
   274  
   275  			line, err := c.prompter.PromptInput(<-scheduler)
   276  			if err != nil {
   277  
   278  				if err == liner.ErrPromptAborted {
   279  					prompt, indents, input = c.prompt, 0, ""
   280  					scheduler <- ""
   281  					continue
   282  				}
   283  				close(scheduler)
   284  				return
   285  			}
   286  
   287  			scheduler <- line
   288  		}
   289  	}()
   290  
   291  	abort := make(chan os.Signal, 1)
   292  	signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM)
   293  
   294  	for {
   295  
   296  		scheduler <- prompt
   297  		select {
   298  		case <-abort:
   299  
   300  			fmt.Fprintln(c.printer, "caught interrupt, exiting")
   301  			return
   302  
   303  		case line, ok := <-scheduler:
   304  
   305  			if !ok || (indents <= 0 && exit.MatchString(line)) {
   306  				return
   307  			}
   308  			if onlyWhitespace.MatchString(line) {
   309  				continue
   310  			}
   311  
   312  			input += line + "\n"
   313  
   314  			indents = countIndents(input)
   315  			if indents <= 0 {
   316  				prompt = c.prompt
   317  			} else {
   318  				prompt = strings.Repeat(".", indents*3) + " "
   319  			}
   320  
   321  			if indents <= 0 {
   322  				if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) {
   323  					if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] {
   324  						c.history = append(c.history, command)
   325  						if c.prompter != nil {
   326  							c.prompter.AppendHistory(command)
   327  						}
   328  					}
   329  				}
   330  				c.Evaluate(input)
   331  				input = ""
   332  			}
   333  		}
   334  	}
   335  }
   336  
   337  func countIndents(input string) int {
   338  	var (
   339  		indents     = 0
   340  		inString    = false
   341  		strOpenChar = ' '
   342  		charEscaped = false
   343  	)
   344  
   345  	for _, c := range input {
   346  		switch c {
   347  		case '\\':
   348  
   349  			if !charEscaped && inString {
   350  				charEscaped = true
   351  			}
   352  		case '\'', '"':
   353  			if inString && !charEscaped && strOpenChar == c {
   354  				inString = false
   355  			} else if !inString && !charEscaped {
   356  				inString = true
   357  				strOpenChar = c
   358  			}
   359  			charEscaped = false
   360  		case '{', '(':
   361  			if !inString {
   362  				indents++
   363  			}
   364  			charEscaped = false
   365  		case '}', ')':
   366  			if !inString {
   367  				indents--
   368  			}
   369  			charEscaped = false
   370  		default:
   371  			charEscaped = false
   372  		}
   373  	}
   374  
   375  	return indents
   376  }
   377  
   378  func (c *Console) Execute(path string) error {
   379  	return c.jsre.Exec(path)
   380  }
   381  
   382  func (c *Console) Stop(graceful bool) error {
   383  	if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
   384  		return err
   385  	}
   386  	if err := os.Chmod(c.histPath, 0600); err != nil {
   387  		return err
   388  	}
   389  	c.jsre.Stop(graceful)
   390  	return nil
   391  }