diff --git a/console/bridge.go b/console/bridge.go deleted file mode 100644 index 37578041ca..0000000000 --- a/console/bridge.go +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package console - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "reflect" - "strings" - "time" - - "github.com/dop251/goja" - "github.com/ethereum/go-ethereum/accounts/scwallet" - "github.com/ethereum/go-ethereum/accounts/usbwallet" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/internal/jsre" - "github.com/ethereum/go-ethereum/rpc" -) - -// bridge is a collection of JavaScript utility methods to bride the .js runtime -// environment and the Go RPC connection backing the remote method calls. -type bridge struct { - client *rpc.Client // RPC client to execute Ethereum requests through - prompter prompt.UserPrompter // Input prompter to allow interactive user feedback - printer io.Writer // Output writer to serialize any display strings to -} - -// newBridge creates a new JavaScript wrapper around an RPC client. -func newBridge(client *rpc.Client, prompter prompt.UserPrompter, printer io.Writer) *bridge { - return &bridge{ - client: client, - prompter: prompter, - printer: printer, - } -} - -func getJeth(vm *goja.Runtime) *goja.Object { - jeth := vm.Get("jeth") - if jeth == nil { - panic(vm.ToValue("jeth object does not exist")) - } - return jeth.ToObject(vm) -} - -// NewAccount is a wrapper around the personal.newAccount RPC method that uses a -// non-echoing password prompt to acquire the passphrase and executes the original -// RPC method (saved in jeth.newAccount) with it to actually execute the RPC call. -func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { - var ( - password string - confirm string - err error - ) - switch { - // No password was specified, prompt the user for it - case len(call.Arguments) == 0: - if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil { - return nil, err - } - if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil { - return nil, err - } - if password != confirm { - return nil, errors.New("passwords don't match") - } - // A single string password was specified, use that - case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil: - password = call.Argument(0).ToString().String() - default: - return nil, errors.New("expected 0 or 1 string argument") - } - // Password acquired, execute the call and return - newAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("newAccount")) - if !callable { - return nil, errors.New("jeth.newAccount is not callable") - } - ret, err := newAccount(goja.Null(), call.VM.ToValue(password)) - if err != nil { - return nil, err - } - return ret, nil -} - -// OpenWallet is a wrapper around personal.openWallet which can interpret and -// react to certain error messages, such as the Trezor PIN matrix request. -func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { - // Make sure we have a wallet specified to open - if call.Argument(0).ToObject(call.VM).ClassName() != "String" { - return nil, errors.New("first argument must be the wallet URL to open") - } - wallet := call.Argument(0) - - var passwd goja.Value - if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) { - passwd = call.VM.ToValue("") - } else { - passwd = call.Argument(1) - } - // Open the wallet and return if successful in itself - openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) - if !callable { - return nil, errors.New("jeth.openWallet is not callable") - } - val, err := openWallet(goja.Null(), wallet, passwd) - if err == nil { - return val, nil - } - - // Wallet open failed, report error unless it's a PIN or PUK entry - switch { - case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()): - val, err = b.readPinAndReopenWallet(call) - if err == nil { - return val, nil - } - val, err = b.readPassphraseAndReopenWallet(call) - if err != nil { - return nil, err - } - - case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()): - // PUK input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter the pairing password: ") - if err != nil { - return nil, err - } - passwd = call.VM.ToValue(input) - if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { - if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { - return nil, err - } - // PIN input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { - return nil, err - } - } - - case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()): - // PIN unblock requested, fetch PUK and new PIN from the user - var pukpin string - input, err := b.prompter.PromptPassword("Please enter current PUK: ") - if err != nil { - return nil, err - } - pukpin = input - input, err = b.prompter.PromptPassword("Please enter new PIN: ") - if err != nil { - return nil, err - } - pukpin += input - - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(pukpin)); err != nil { - return nil, err - } - - case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()): - // PIN input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { - return nil, err - } - - default: - // Unknown error occurred, drop to the user - return nil, err - } - return val, nil -} - -func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, error) { - wallet := call.Argument(0) - input, err := b.prompter.PromptPassword("Please enter your passphrase: ") - if err != nil { - return nil, err - } - openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) - if !callable { - return nil, errors.New("jeth.openWallet is not callable") - } - return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) -} - -func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) { - wallet := call.Argument(0) - // Trezor PIN matrix input requested, display the matrix to the user and fetch the data - fmt.Fprintf(b.printer, "Look at the device for number positions\n\n") - fmt.Fprintf(b.printer, "7 | 8 | 9\n") - fmt.Fprintf(b.printer, "--+---+--\n") - fmt.Fprintf(b.printer, "4 | 5 | 6\n") - fmt.Fprintf(b.printer, "--+---+--\n") - fmt.Fprintf(b.printer, "1 | 2 | 3\n\n") - - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) - if !callable { - return nil, errors.New("jeth.openWallet is not callable") - } - return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) -} - -// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that -// uses a non-echoing password prompt to acquire the passphrase and executes the -// original RPC method (saved in jeth.unlockAccount) with it to actually execute -// the RPC call. -func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { - if len(call.Arguments) < 1 { - return nil, errors.New("usage: unlockAccount(account, [ password, duration ])") - } - - account := call.Argument(0) - // Make sure we have an account specified to unlock. - if goja.IsUndefined(account) || goja.IsNull(account) || account.ExportType().Kind() != reflect.String { - return nil, errors.New("first argument must be the account to unlock") - } - - // If password is not given or is the null value, prompt the user for it. - var passwd goja.Value - if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) { - fmt.Fprintf(b.printer, "Unlock account %s\n", account) - input, err := b.prompter.PromptPassword("Passphrase: ") - if err != nil { - return nil, err - } - passwd = call.VM.ToValue(input) - } else { - if call.Argument(1).ExportType().Kind() != reflect.String { - return nil, errors.New("password must be a string") - } - passwd = call.Argument(1) - } - - // Third argument is the duration how long the account should be unlocked. - duration := goja.Null() - if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) { - if !isNumber(call.Argument(2)) { - return nil, errors.New("unlock duration must be a number") - } - duration = call.Argument(2) - } - - // Send the request to the backend and return. - unlockAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount")) - if !callable { - return nil, errors.New("jeth.unlockAccount is not callable") - } - return unlockAccount(goja.Null(), account, passwd, duration) -} - -// Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password -// prompt to acquire the passphrase and executes the original RPC method (saved in -// jeth.sign) with it to actually execute the RPC call. -func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { - if nArgs := len(call.Arguments); nArgs < 2 { - return nil, errors.New("usage: sign(message, account, [ password ])") - } - var ( - message = call.Argument(0) - account = call.Argument(1) - passwd = call.Argument(2) - ) - - if goja.IsUndefined(message) || message.ExportType().Kind() != reflect.String { - return nil, errors.New("first argument must be the message to sign") - } - if goja.IsUndefined(account) || account.ExportType().Kind() != reflect.String { - return nil, errors.New("second argument must be the account to sign with") - } - - // if the password is not given or null ask the user and ensure password is a string - if goja.IsUndefined(passwd) || goja.IsNull(passwd) { - fmt.Fprintf(b.printer, "Give password for account %s\n", account) - input, err := b.prompter.PromptPassword("Password: ") - if err != nil { - return nil, err - } - passwd = call.VM.ToValue(input) - } else if passwd.ExportType().Kind() != reflect.String { - return nil, errors.New("third argument must be the password to unlock the account") - } - - // Send the request to the backend and return - sign, callable := goja.AssertFunction(getJeth(call.VM).Get("sign")) - if !callable { - return nil, errors.New("jeth.sign is not callable") - } - return sign(goja.Null(), message, account, passwd) -} - -// Sleep will block the console for the specified number of seconds. -func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) { - if nArgs := len(call.Arguments); nArgs < 1 { - return nil, errors.New("usage: sleep()") - } - sleepObj := call.Argument(0) - if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) { - return nil, errors.New("usage: sleep()") - } - sleep := sleepObj.ToFloat() - time.Sleep(time.Duration(sleep * float64(time.Second))) - return call.VM.ToValue(true), nil -} - -// SleepBlocks will block the console for a specified number of new blocks optionally -// until the given timeout is reached. -func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { - // Parse the input parameters for the sleep. - var ( - blocks = int64(0) - sleep = int64(9999999999999999) // indefinitely - ) - nArgs := len(call.Arguments) - if nArgs == 0 { - return nil, errors.New("usage: sleepBlocks([, max sleep in seconds])") - } - if nArgs >= 1 { - if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) { - return nil, errors.New("expected number as first argument") - } - blocks = call.Argument(0).ToInteger() - } - if nArgs >= 2 { - if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) { - return nil, errors.New("expected number as second argument") - } - sleep = call.Argument(1).ToInteger() - } - - // Poll the current block number until either it or a timeout is reached. - deadline := time.Now().Add(time.Duration(sleep) * time.Second) - var lastNumber hexutil.Uint64 - if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { - return nil, err - } - for time.Now().Before(deadline) { - var number hexutil.Uint64 - if err := b.client.Call(&number, "eth_blockNumber"); err != nil { - return nil, err - } - if number != lastNumber { - lastNumber = number - blocks-- - } - if blocks <= 0 { - break - } - time.Sleep(time.Second) - } - return call.VM.ToValue(true), nil -} - -type jsonrpcCall struct { - ID int64 - Method string - Params []interface{} -} - -// Send implements the web3 provider "send" method. -func (b *bridge) Send(call jsre.Call) (goja.Value, error) { - // Remarshal the request into a Go value. - reqVal, err := call.Argument(0).ToObject(call.VM).MarshalJSON() - if err != nil { - return nil, err - } - - var ( - rawReq = string(reqVal) - dec = json.NewDecoder(strings.NewReader(rawReq)) - reqs []jsonrpcCall - batch bool - ) - dec.UseNumber() // avoid float64s - if rawReq[0] == '[' { - batch = true - dec.Decode(&reqs) - } else { - batch = false - reqs = make([]jsonrpcCall, 1) - dec.Decode(&reqs[0]) - } - - // Execute the requests. - var resps []*goja.Object - for _, req := range reqs { - resp := call.VM.NewObject() - resp.Set("jsonrpc", "2.0") - resp.Set("id", req.ID) - - var result json.RawMessage - if err = b.client.Call(&result, req.Method, req.Params...); err == nil { - if result == nil { - // Special case null because it is decoded as an empty - // raw message for some reason. - resp.Set("result", goja.Null()) - } else { - JSON := call.VM.Get("JSON").ToObject(call.VM) - parse, callable := goja.AssertFunction(JSON.Get("parse")) - if !callable { - return nil, errors.New("JSON.parse is not a function") - } - resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) - if err != nil { - setError(resp, -32603, err.Error(), nil) - } else { - resp.Set("result", resultVal) - } - } - } else { - code := -32603 - var data interface{} - if err, ok := err.(rpc.Error); ok { - code = err.ErrorCode() - } - if err, ok := err.(rpc.DataError); ok { - data = err.ErrorData() - } - setError(resp, code, err.Error(), data) - } - resps = append(resps, resp) - } - // Return the responses either to the callback (if supplied) - // or directly as the return value. - var result goja.Value - if batch { - result = call.VM.ToValue(resps) - } else { - result = resps[0] - } - if fn, isFunc := goja.AssertFunction(call.Argument(1)); isFunc { - fn(goja.Null(), goja.Null(), result) - return goja.Undefined(), nil - } - return result, nil -} - -func setError(resp *goja.Object, code int, msg string, data interface{}) { - err := make(map[string]interface{}) - err["code"] = code - err["message"] = msg - if data != nil { - err["data"] = data - } - resp.Set("error", err) -} - -// isNumber returns true if input value is a JS number. -func isNumber(v goja.Value) bool { - k := v.ExportType().Kind() - return k >= reflect.Int && k <= reflect.Float64 -} - -func getObject(vm *goja.Runtime, name string) *goja.Object { - v := vm.Get(name) - if v == nil { - return nil - } - return v.ToObject(vm) -} diff --git a/console/bridge_test.go b/console/bridge_test.go deleted file mode 100644 index e57e294fc5..0000000000 --- a/console/bridge_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package console - -import ( - "testing" - - "github.com/dop251/goja" - "github.com/ethereum/go-ethereum/internal/jsre" -) - -// TestUndefinedAsParam ensures that personal functions can receive -// `undefined` as a parameter. -func TestUndefinedAsParam(t *testing.T) { - b := bridge{} - call := jsre.Call{} - call.Arguments = []goja.Value{goja.Undefined()} - - b.UnlockAccount(call) - b.Sign(call) - b.Sleep(call) -} - -// TestNullAsParam ensures that personal functions can receive -// `null` as a parameter. -func TestNullAsParam(t *testing.T) { - b := bridge{} - call := jsre.Call{} - call.Arguments = []goja.Value{goja.Null()} - - b.UnlockAccount(call) - b.Sign(call) - b.Sleep(call) -} diff --git a/console/console.go b/console/console.go deleted file mode 100644 index cdee53684e..0000000000 --- a/console/console.go +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package console - -import ( - "errors" - "fmt" - "io" - "os" - "os/signal" - "path/filepath" - "regexp" - "sort" - "strings" - "sync" - "syscall" - - "github.com/dop251/goja" - "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/internal/jsre" - "github.com/ethereum/go-ethereum/internal/jsre/deps" - "github.com/ethereum/go-ethereum/internal/web3ext" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/mattn/go-colorable" - "github.com/peterh/liner" -) - -var ( - // u: unlock, s: signXX, sendXX, n: newAccount, i: importXX - passwordRegexp = regexp.MustCompile(`personal.[nusi]`) - onlyWhitespace = regexp.MustCompile(`^\s*$`) - exit = regexp.MustCompile(`^\s*exit\s*;*\s*$`) -) - -// HistoryFile is the file within the data directory to store input scrollback. -const HistoryFile = "history" - -// DefaultPrompt is the default prompt line prefix to use for user input querying. -const DefaultPrompt = "> " - -// Config is the collection of configurations to fine tune the behavior of the -// JavaScript console. -type Config struct { - DataDir string // Data directory to store the console history at - DocRoot string // Filesystem path from where to load JavaScript files from - Client *rpc.Client // RPC client to execute Ethereum requests through - Prompt string // Input prompt prefix string (defaults to DefaultPrompt) - Prompter prompt.UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter) - Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout) - Preload []string // Absolute paths to JavaScript files to preload -} - -// Console is a JavaScript interpreted runtime environment. It is a fully fledged -// JavaScript console attached to a running node via an external or in-process RPC -// client. -type Console struct { - client *rpc.Client // RPC client to execute Ethereum requests through - jsre *jsre.JSRE // JavaScript runtime environment running the interpreter - prompt string // Input prompt prefix string - prompter prompt.UserPrompter // Input prompter to allow interactive user feedback - histPath string // Absolute path to the console scrollback history - history []string // Scroll history maintained by the console - printer io.Writer // Output writer to serialize any display strings to - - interactiveStopped chan struct{} - stopInteractiveCh chan struct{} - signalReceived chan struct{} - stopped chan struct{} - wg sync.WaitGroup - stopOnce sync.Once -} - -// New initializes a JavaScript interpreted runtime environment and sets defaults -// with the config struct. -func New(config Config) (*Console, error) { - // Handle unset config values gracefully - if config.Prompter == nil { - config.Prompter = prompt.Stdin - } - if config.Prompt == "" { - config.Prompt = DefaultPrompt - } - if config.Printer == nil { - config.Printer = colorable.NewColorableStdout() - } - - // Initialize the console and return - console := &Console{ - client: config.Client, - jsre: jsre.New(config.DocRoot, config.Printer), - prompt: config.Prompt, - prompter: config.Prompter, - printer: config.Printer, - histPath: filepath.Join(config.DataDir, HistoryFile), - interactiveStopped: make(chan struct{}), - stopInteractiveCh: make(chan struct{}), - signalReceived: make(chan struct{}, 1), - stopped: make(chan struct{}), - } - if err := os.MkdirAll(config.DataDir, 0700); err != nil { - return nil, err - } - if err := console.init(config.Preload); err != nil { - return nil, err - } - - console.wg.Add(1) - go console.interruptHandler() - - return console, nil -} - -// init retrieves the available APIs from the remote RPC provider and initializes -// the console's JavaScript namespaces based on the exposed modules. -func (c *Console) init(preload []string) error { - c.initConsoleObject() - - // Initialize the JavaScript <-> Go RPC bridge. - bridge := newBridge(c.client, c.prompter, c.printer) - if err := c.initWeb3(bridge); err != nil { - return err - } - if err := c.initExtensions(); err != nil { - return err - } - - // Add bridge overrides for web3.js functionality. - c.jsre.Do(func(vm *goja.Runtime) { - c.initAdmin(vm, bridge) - c.initPersonal(vm, bridge) - }) - - // Preload JavaScript files. - for _, path := range preload { - if err := c.jsre.Exec(path); err != nil { - failure := err.Error() - if gojaErr, ok := err.(*goja.Exception); ok { - failure = gojaErr.String() - } - return fmt.Errorf("%s: %v", path, failure) - } - } - - // Configure the input prompter for history and tab completion. - if c.prompter != nil { - if content, err := os.ReadFile(c.histPath); err != nil { - c.prompter.SetHistory(nil) - } else { - c.history = strings.Split(string(content), "\n") - c.prompter.SetHistory(c.history) - } - c.prompter.SetWordCompleter(c.AutoCompleteInput) - } - return nil -} - -func (c *Console) initConsoleObject() { - c.jsre.Do(func(vm *goja.Runtime) { - console := vm.NewObject() - console.Set("log", c.consoleOutput) - console.Set("error", c.consoleOutput) - vm.Set("console", console) - }) -} - -func (c *Console) initWeb3(bridge *bridge) error { - if err := c.jsre.Compile("bignumber.js", deps.BigNumberJS); err != nil { - return fmt.Errorf("bignumber.js: %v", err) - } - if err := c.jsre.Compile("web3.js", deps.Web3JS); err != nil { - return fmt.Errorf("web3.js: %v", err) - } - if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil { - return fmt.Errorf("web3 require: %v", err) - } - var err error - c.jsre.Do(func(vm *goja.Runtime) { - transport := vm.NewObject() - transport.Set("send", jsre.MakeCallback(vm, bridge.Send)) - transport.Set("sendAsync", jsre.MakeCallback(vm, bridge.Send)) - vm.Set("_consoleWeb3Transport", transport) - _, err = vm.RunString("var web3 = new Web3(_consoleWeb3Transport)") - }) - return err -} - -var defaultAPIs = map[string]string{"eth": "1.0", "net": "1.0", "debug": "1.0"} - -// initExtensions loads and registers web3.js extensions. -func (c *Console) initExtensions() error { - const methodNotFound = -32601 - apis, err := c.client.SupportedModules() - if err != nil { - if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == methodNotFound { - log.Warn("Server does not support method rpc_modules, using default API list.") - apis = defaultAPIs - } else { - return err - } - } - - // Compute aliases from server-provided modules. - aliases := map[string]struct{}{"eth": {}} - for api := range apis { - if api == "web3" { - continue - } - aliases[api] = struct{}{} - if file, ok := web3ext.Modules[api]; ok { - if err = c.jsre.Compile(api+".js", file); err != nil { - return fmt.Errorf("%s.js: %v", api, err) - } - } - } - - // Apply aliases. - c.jsre.Do(func(vm *goja.Runtime) { - web3 := getObject(vm, "web3") - for name := range aliases { - if v := web3.Get(name); v != nil { - vm.Set(name, v) - } - } - }) - return nil -} - -// initAdmin creates additional admin APIs implemented by the bridge. -func (c *Console) initAdmin(vm *goja.Runtime, bridge *bridge) { - if admin := getObject(vm, "admin"); admin != nil { - admin.Set("sleepBlocks", jsre.MakeCallback(vm, bridge.SleepBlocks)) - admin.Set("sleep", jsre.MakeCallback(vm, bridge.Sleep)) - admin.Set("clearHistory", c.clearHistory) - } -} - -// initPersonal redirects account-related API methods through the bridge. -// -// If the console is in interactive mode and the 'personal' API is available, override -// the openWallet, unlockAccount, newAccount and sign methods since these require user -// interaction. The original web3 callbacks are stored in 'jeth'. These will be called -// by the bridge after the prompt and send the original web3 request to the backend. -func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) { - personal := getObject(vm, "personal") - if personal == nil || c.prompter == nil { - return - } - log.Warn("Enabling deprecated personal namespace") - jeth := vm.NewObject() - vm.Set("jeth", jeth) - jeth.Set("openWallet", personal.Get("openWallet")) - jeth.Set("unlockAccount", personal.Get("unlockAccount")) - jeth.Set("newAccount", personal.Get("newAccount")) - jeth.Set("sign", personal.Get("sign")) - personal.Set("openWallet", jsre.MakeCallback(vm, bridge.OpenWallet)) - personal.Set("unlockAccount", jsre.MakeCallback(vm, bridge.UnlockAccount)) - personal.Set("newAccount", jsre.MakeCallback(vm, bridge.NewAccount)) - personal.Set("sign", jsre.MakeCallback(vm, bridge.Sign)) -} - -func (c *Console) clearHistory() { - c.history = nil - c.prompter.ClearHistory() - if err := os.Remove(c.histPath); err != nil { - fmt.Fprintln(c.printer, "can't delete history file:", err) - } else { - fmt.Fprintln(c.printer, "history file deleted") - } -} - -// consoleOutput is an override for the console.log and console.error methods to -// stream the output into the configured output stream instead of stdout. -func (c *Console) consoleOutput(call goja.FunctionCall) goja.Value { - var output []string - for _, argument := range call.Arguments { - output = append(output, fmt.Sprintf("%v", argument)) - } - fmt.Fprintln(c.printer, strings.Join(output, " ")) - return goja.Null() -} - -// AutoCompleteInput is a pre-assembled word completer to be used by the user -// input prompter to provide hints to the user about the methods available. -func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) { - // No completions can be provided for empty inputs - if len(line) == 0 || pos == 0 { - return "", nil, "" - } - // Chunk data to relevant part for autocompletion - // E.g. in case of nested lines eth.getBalance(eth.coinb - start := pos - 1 - for ; start > 0; start-- { - // Skip all methods and namespaces (i.e. including the dot) - c := line[start] - if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') { - continue - } - // We've hit an unexpected character, autocomplete form here - start++ - break - } - return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] -} - -// Welcome show summary of current Geth instance and some metadata about the -// console's available modules. -func (c *Console) Welcome() { - message := "Welcome to the Geth JavaScript console!\n\n" - - // Print some generic Geth metadata - if res, err := c.jsre.Run(` - var message = "instance: " + web3.version.node + "\n"; - try { - message += "coinbase: " + eth.coinbase + "\n"; - } catch (err) {} - message += "at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")\n"; - try { - message += " datadir: " + admin.datadir + "\n"; - } catch (err) {} - message - `); err == nil { - message += res.String() - } - // List all the supported modules for the user to call - if apis, err := c.client.SupportedModules(); err == nil { - modules := make([]string, 0, len(apis)) - for api, version := range apis { - modules = append(modules, fmt.Sprintf("%s:%s", api, version)) - } - sort.Strings(modules) - message += " modules: " + strings.Join(modules, " ") + "\n" - } - message += "\nTo exit, press ctrl-d or type exit" - fmt.Fprintln(c.printer, message) -} - -// Evaluate executes code and pretty prints the result to the specified output -// stream. -func (c *Console) Evaluate(statement string) { - defer func() { - if r := recover(); r != nil { - fmt.Fprintf(c.printer, "[native] error: %v\n", r) - } - }() - c.jsre.Evaluate(statement, c.printer) - - // Avoid exiting Interactive when jsre was interrupted by SIGINT. - c.clearSignalReceived() -} - -// interruptHandler runs in its own goroutine and waits for signals. -// When a signal is received, it interrupts the JS interpreter. -func (c *Console) interruptHandler() { - defer c.wg.Done() - - // During Interactive, liner inhibits the signal while it is prompting for - // input. However, the signal will be received while evaluating JS. - // - // On unsupported terminals, SIGINT can also happen while prompting. - // Unfortunately, it is not possible to abort the prompt in this case and - // the c.readLines goroutine leaks. - sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGINT) - defer signal.Stop(sig) - - for { - select { - case <-sig: - c.setSignalReceived() - c.jsre.Interrupt(errors.New("interrupted")) - case <-c.stopInteractiveCh: - close(c.interactiveStopped) - c.jsre.Interrupt(errors.New("interrupted")) - case <-c.stopped: - return - } - } -} - -func (c *Console) setSignalReceived() { - select { - case c.signalReceived <- struct{}{}: - default: - } -} - -func (c *Console) clearSignalReceived() { - select { - case <-c.signalReceived: - default: - } -} - -// StopInteractive causes Interactive to return as soon as possible. -func (c *Console) StopInteractive() { - select { - case c.stopInteractiveCh <- struct{}{}: - case <-c.stopped: - } -} - -// Interactive starts an interactive user session, where input is prompted from -// the configured user prompter. -func (c *Console) Interactive() { - var ( - prompt = c.prompt // the current prompt line (used for multi-line inputs) - indents = 0 // the current number of input indents (used for multi-line inputs) - input = "" // the current user input - inputLine = make(chan string, 1) // receives user input - inputErr = make(chan error, 1) // receives liner errors - requestLine = make(chan string) // requests a line of input - ) - - defer func() { - c.writeHistory() - }() - - // The line reader runs in a separate goroutine. - go c.readLines(inputLine, inputErr, requestLine) - defer close(requestLine) - - for { - // Send the next prompt, triggering an input read. - requestLine <- prompt - - select { - case <-c.interactiveStopped: - fmt.Fprintln(c.printer, "node is down, exiting console") - return - - case <-c.signalReceived: - // SIGINT received while prompting for input -> unsupported terminal. - // I'm not sure if the best choice would be to leave the console running here. - // Bash keeps running in this case. node.js does not. - fmt.Fprintln(c.printer, "caught interrupt, exiting") - return - - case err := <-inputErr: - if err == liner.ErrPromptAborted { - // When prompting for multi-line input, the first Ctrl-C resets - // the multi-line state. - prompt, indents, input = c.prompt, 0, "" - continue - } - return - - case line := <-inputLine: - // User input was returned by the prompter, handle special cases. - if indents <= 0 && exit.MatchString(line) { - return - } - if onlyWhitespace.MatchString(line) { - continue - } - // Append the line to the input and check for multi-line interpretation. - input += line + "\n" - indents = countIndents(input) - if indents <= 0 { - prompt = c.prompt - } else { - prompt = strings.Repeat(".", indents*3) + " " - } - // If all the needed lines are present, save the command and run it. - if indents <= 0 { - if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { - if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { - c.history = append(c.history, command) - if c.prompter != nil { - c.prompter.AppendHistory(command) - } - } - } - c.Evaluate(input) - input = "" - } - } - } -} - -// readLines runs in its own goroutine, prompting for input. -func (c *Console) readLines(input chan<- string, errc chan<- error, prompt <-chan string) { - for p := range prompt { - line, err := c.prompter.PromptInput(p) - if err != nil { - errc <- err - } else { - input <- line - } - } -} - -// countIndents returns the number of indentations for the given input. -// In case of invalid input such as var a = } the result can be negative. -func countIndents(input string) int { - var ( - indents = 0 - inString = false - strOpenChar = ' ' // keep track of the string open char to allow var str = "I'm ...."; - charEscaped = false // keep track if the previous char was the '\' char, allow var str = "abc\"def"; - ) - - for _, c := range input { - switch c { - case '\\': - // indicate next char as escaped when in string and previous char isn't escaping this backslash - if !charEscaped && inString { - charEscaped = true - } - case '\'', '"': - if inString && !charEscaped && strOpenChar == c { // end string - inString = false - } else if !inString && !charEscaped { // begin string - inString = true - strOpenChar = c - } - charEscaped = false - case '{', '(': - if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting - indents++ - } - charEscaped = false - case '}', ')': - if !inString { - indents-- - } - charEscaped = false - default: - charEscaped = false - } - } - - return indents -} - -// Stop cleans up the console and terminates the runtime environment. -func (c *Console) Stop(graceful bool) error { - c.stopOnce.Do(func() { - // Stop the interrupt handler. - close(c.stopped) - c.wg.Wait() - }) - - c.jsre.Stop(graceful) - return nil -} - -func (c *Console) writeHistory() error { - if err := os.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { - return err - } - return os.Chmod(c.histPath, 0600) // Force 0600, even if it was different previously -} diff --git a/console/console_test.go b/console/console_test.go deleted file mode 100644 index a13be6a99d..0000000000 --- a/console/console_test.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package console - -import ( - "bytes" - "errors" - "fmt" - "os" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/internal/jsre" - "github.com/ethereum/go-ethereum/miner" - "github.com/ethereum/go-ethereum/node" -) - -const ( - testInstance = "console-tester" - testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" -) - -// hookedPrompter implements UserPrompter to simulate use input via channels. -type hookedPrompter struct { - scheduler chan string -} - -func (p *hookedPrompter) PromptInput(prompt string) (string, error) { - // Send the prompt to the tester - select { - case p.scheduler <- prompt: - case <-time.After(time.Second): - return "", errors.New("prompt timeout") - } - // Retrieve the response and feed to the console - select { - case input := <-p.scheduler: - return input, nil - case <-time.After(time.Second): - return "", errors.New("input timeout") - } -} - -func (p *hookedPrompter) PromptPassword(prompt string) (string, error) { - return "", errors.New("not implemented") -} -func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) { - return false, errors.New("not implemented") -} -func (p *hookedPrompter) SetHistory(history []string) {} -func (p *hookedPrompter) AppendHistory(command string) {} -func (p *hookedPrompter) ClearHistory() {} -func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {} - -// tester is a console test environment for the console tests to operate on. -type tester struct { - workspace string - stack *node.Node - ethereum *eth.Ethereum - console *Console - input *hookedPrompter - output *bytes.Buffer -} - -// newTester creates a test environment based on which the console can operate. -// Please ensure you call Close() on the returned tester to avoid leaks. -func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { - // Create a temporary storage for the node keys and initialize it - workspace := t.TempDir() - - // Create a networkless protocol stack and start an Ethereum service within - stack, err := node.New(&node.Config{DataDir: workspace, UseLightweightKDF: true, Name: testInstance}) - if err != nil { - t.Fatalf("failed to create node: %v", err) - } - ethConf := ðconfig.Config{ - Genesis: core.DeveloperGenesisBlock(11_500_000, nil), - Miner: miner.Config{ - Etherbase: common.HexToAddress(testAddress), - }, - } - if confOverride != nil { - confOverride(ethConf) - } - ethBackend, err := eth.New(stack, ethConf) - if err != nil { - t.Fatalf("failed to register Ethereum protocol: %v", err) - } - // Start the node and assemble the JavaScript console around it - if err = stack.Start(); err != nil { - t.Fatalf("failed to start test stack: %v", err) - } - client := stack.Attach() - t.Cleanup(func() { - client.Close() - }) - - prompter := &hookedPrompter{scheduler: make(chan string)} - printer := new(bytes.Buffer) - - console, err := New(Config{ - DataDir: stack.DataDir(), - DocRoot: "testdata", - Client: client, - Prompter: prompter, - Printer: printer, - Preload: []string{"preload.js"}, - }) - if err != nil { - t.Fatalf("failed to create JavaScript console: %v", err) - } - // Create the final tester and return - return &tester{ - workspace: workspace, - stack: stack, - ethereum: ethBackend, - console: console, - input: prompter, - output: printer, - } -} - -// Close cleans up any temporary data folders and held resources. -func (env *tester) Close(t *testing.T) { - if err := env.console.Stop(false); err != nil { - t.Errorf("failed to stop embedded console: %v", err) - } - if err := env.stack.Close(); err != nil { - t.Errorf("failed to tear down embedded node: %v", err) - } - os.RemoveAll(env.workspace) -} - -// Tests that the node lists the correct welcome message, notably that it contains -// the instance name, coinbase account, block number, data directory and supported -// console modules. -func TestWelcome(t *testing.T) { - tester := newTester(t, nil) - defer tester.Close(t) - - tester.console.Welcome() - - output := tester.output.String() - if want := "Welcome"; !strings.Contains(output, want) { - t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want) - } - if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) { - t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want) - } - if want := fmt.Sprintf("coinbase: %s", testAddress); !strings.Contains(output, want) { - t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) - } - if want := "at block: 0"; !strings.Contains(output, want) { - t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want) - } - if want := fmt.Sprintf("datadir: %s", tester.workspace); !strings.Contains(output, want) { - t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) - } -} - -// Tests that JavaScript statement evaluation works as intended. -func TestEvaluate(t *testing.T) { - tester := newTester(t, nil) - defer tester.Close(t) - - tester.console.Evaluate("2 + 2") - if output := tester.output.String(); !strings.Contains(output, "4") { - t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") - } -} - -// Tests that the console can be used in interactive mode. -func TestInteractive(t *testing.T) { - // Create a tester and run an interactive console in the background - tester := newTester(t, nil) - defer tester.Close(t) - - go tester.console.Interactive() - - // Wait for a prompt and send a statement back - select { - case <-tester.input.scheduler: - case <-time.After(time.Second): - t.Fatalf("initial prompt timeout") - } - select { - case tester.input.scheduler <- "2+2": - case <-time.After(time.Second): - t.Fatalf("input feedback timeout") - } - // Wait for the second prompt and ensure first statement was evaluated - select { - case <-tester.input.scheduler: - case <-time.After(time.Second): - t.Fatalf("secondary prompt timeout") - } - if output := tester.output.String(); !strings.Contains(output, "4") { - t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") - } -} - -// Tests that preloaded JavaScript files have been executed before user is given -// input. -func TestPreload(t *testing.T) { - tester := newTester(t, nil) - defer tester.Close(t) - - tester.console.Evaluate("preloaded") - if output := tester.output.String(); !strings.Contains(output, "some-preloaded-string") { - t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string") - } -} - -// Tests that the JavaScript objects returned by statement executions are properly -// pretty printed instead of just displaying "[object]". -func TestPrettyPrint(t *testing.T) { - tester := newTester(t, nil) - defer tester.Close(t) - - tester.console.Evaluate("obj = {int: 1, string: 'two', list: [3, 3, 3], obj: {null: null, func: function(){}}}") - - // Define some specially formatted fields - var ( - one = jsre.NumberColor("1") - two = jsre.StringColor("\"two\"") - three = jsre.NumberColor("3") - null = jsre.SpecialColor("null") - fun = jsre.FunctionColor("function()") - ) - // Assemble the actual output we're after and verify - want := `{ - int: ` + one + `, - list: [` + three + `, ` + three + `, ` + three + `], - obj: { - null: ` + null + `, - func: ` + fun + ` - }, - string: ` + two + ` -} -` - if output := tester.output.String(); output != want { - t.Fatalf("pretty print mismatch: have %s, want %s", output, want) - } -} - -// Tests that the JavaScript exceptions are properly formatted and colored. -func TestPrettyError(t *testing.T) { - tester := newTester(t, nil) - defer tester.Close(t) - tester.console.Evaluate("throw 'hello'") - - want := jsre.ErrorColor("hello") + "\n\tat :1:1(1)\n\n" - if output := tester.output.String(); output != want { - t.Fatalf("pretty error mismatch: have %s, want %s", output, want) - } -} - -// Tests that tests if the number of indents for JS input is calculated correct. -func TestIndenting(t *testing.T) { - testCases := []struct { - input string - expectedIndentCount int - }{ - {`var a = 1;`, 0}, - {`"some string"`, 0}, - {`"some string with (parenthesis`, 0}, - {`"some string with newline - ("`, 0}, - {`function v(a,b) {}`, 0}, - {`function f(a,b) { var str = "asd("; };`, 0}, - {`function f(a) {`, 1}, - {`function f(a, function(b) {`, 2}, - {`function f(a, function(b) { - var str = "a)}"; - });`, 0}, - {`function f(a,b) { - var str = "a{b(" + a, ", " + b; - }`, 0}, - {`var str = "\"{"`, 0}, - {`var str = "'("`, 0}, - {`var str = "\\{"`, 0}, - {`var str = "\\\\{"`, 0}, - {`var str = 'a"{`, 0}, - {`var obj = {`, 1}, - {`var obj = { {a:1`, 2}, - {`var obj = { {a:1}`, 1}, - {`var obj = { {a:1}, b:2}`, 0}, - {`var obj = {}`, 0}, - {`var obj = { - a: 1, b: 2 - }`, 0}, - {`var test = }`, -1}, - {`var str = "a\""; var obj = {`, 1}, - } - - for i, tt := range testCases { - counted := countIndents(tt.input) - if counted != tt.expectedIndentCount { - t.Errorf("test %d: invalid indenting: have %d, want %d", i, counted, tt.expectedIndentCount) - } - } -} diff --git a/console/prompt/prompter.go b/console/prompt/prompter.go deleted file mode 100644 index 2a20b6906a..0000000000 --- a/console/prompt/prompter.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package prompt - -import ( - "fmt" - "strings" - - "github.com/peterh/liner" -) - -// Stdin holds the stdin line reader (also using stdout for printing prompts). -// Only this reader may be used for input because it keeps an internal buffer. -var Stdin = newTerminalPrompter() - -// UserPrompter defines the methods needed by the console to prompt the user for -// various types of inputs. -type UserPrompter interface { - // PromptInput displays the given prompt to the user and requests some textual - // data to be entered, returning the input of the user. - PromptInput(prompt string) (string, error) - - // PromptPassword displays the given prompt to the user and requests some textual - // data to be entered, but one which must not be echoed out into the terminal. - // The method returns the input provided by the user. - PromptPassword(prompt string) (string, error) - - // PromptConfirm displays the given prompt to the user and requests a boolean - // choice to be made, returning that choice. - PromptConfirm(prompt string) (bool, error) - - // SetHistory sets the input scrollback history that the prompter will allow - // the user to scroll back to. - SetHistory(history []string) - - // AppendHistory appends an entry to the scrollback history. It should be called - // if and only if the prompt to append was a valid command. - AppendHistory(command string) - - // ClearHistory clears the entire history - ClearHistory() - - // SetWordCompleter sets the completion function that the prompter will call to - // fetch completion candidates when the user presses tab. - SetWordCompleter(completer WordCompleter) -} - -// WordCompleter takes the currently edited line with the cursor position and -// returns the completion candidates for the partial word to be completed. If -// the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, -// wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world", -// "Word"}, "!!!") to have "Hello, world!!!". -type WordCompleter func(line string, pos int) (string, []string, string) - -// terminalPrompter is a UserPrompter backed by the liner package. It supports -// prompting the user for various input, among others for non-echoing password -// input. -type terminalPrompter struct { - *liner.State - warned bool - supported bool - normalMode liner.ModeApplier - rawMode liner.ModeApplier -} - -// newTerminalPrompter creates a liner based user input prompter working off the -// standard input and output streams. -func newTerminalPrompter() *terminalPrompter { - p := new(terminalPrompter) - // Get the original mode before calling NewLiner. - // This is usually regular "cooked" mode where characters echo. - normalMode, _ := liner.TerminalMode() - // Turn on liner. It switches to raw mode. - p.State = liner.NewLiner() - rawMode, err := liner.TerminalMode() - if err != nil || !liner.TerminalSupported() { - p.supported = false - } else { - p.supported = true - p.normalMode = normalMode - p.rawMode = rawMode - // Switch back to normal mode while we're not prompting. - normalMode.ApplyMode() - } - p.SetCtrlCAborts(true) - p.SetTabCompletionStyle(liner.TabPrints) - p.SetMultiLineMode(true) - return p -} - -// PromptInput displays the given prompt to the user and requests some textual -// data to be entered, returning the input of the user. -func (p *terminalPrompter) PromptInput(prompt string) (string, error) { - if p.supported { - p.rawMode.ApplyMode() - defer p.normalMode.ApplyMode() - } else { - // liner tries to be smart about printing the prompt - // and doesn't print anything if input is redirected. - // Un-smart it by printing the prompt always. - fmt.Print(prompt) - prompt = "" - defer fmt.Println() - } - return p.State.Prompt(prompt) -} - -// PromptPassword displays the given prompt to the user and requests some textual -// data to be entered, but one which must not be echoed out into the terminal. -// The method returns the input provided by the user. -func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) { - if p.supported { - p.rawMode.ApplyMode() - defer p.normalMode.ApplyMode() - return p.State.PasswordPrompt(prompt) - } - if !p.warned { - fmt.Println("!! Unsupported terminal, password will be echoed.") - p.warned = true - } - // Just as in Prompt, handle printing the prompt here instead of relying on liner. - fmt.Print(prompt) - passwd, err = p.State.Prompt("") - fmt.Println() - return passwd, err -} - -// PromptConfirm displays the given prompt to the user and requests a boolean -// choice to be made, returning that choice. -func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { - input, err := p.Prompt(prompt + " [y/n] ") - if len(input) > 0 && strings.EqualFold(input[:1], "y") { - return true, nil - } - return false, err -} - -// SetHistory sets the input scrollback history that the prompter will allow -// the user to scroll back to. -func (p *terminalPrompter) SetHistory(history []string) { - p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) -} - -// AppendHistory appends an entry to the scrollback history. -func (p *terminalPrompter) AppendHistory(command string) { - p.State.AppendHistory(command) -} - -// ClearHistory clears the entire history -func (p *terminalPrompter) ClearHistory() { - p.State.ClearHistory() -} - -// SetWordCompleter sets the completion function that the prompter will call to -// fetch completion candidates when the user presses tab. -func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) { - p.State.SetWordCompleter(liner.WordCompleter(completer)) -} diff --git a/console/testdata/preload.js b/console/testdata/preload.js deleted file mode 100644 index 556793970f..0000000000 --- a/console/testdata/preload.js +++ /dev/null @@ -1 +0,0 @@ -var preloaded = "some-preloaded-string";