console, internal/jsre: use github.com/dop251/goja (#20470)
This replaces the JavaScript interpreter used by the console with goja, which is actively maintained and a lot faster than otto. Clef still uses otto and eth/tracers still uses duktape, so we are currently dependent on three different JS interpreters. We're looking to replace the remaining uses of otto soon though.
This commit is contained in:
parent
60deeb103e
commit
7b68975a00
@ -51,7 +51,9 @@ func TestConsoleWelcome(t *testing.T) {
|
|||||||
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
geth.SetTemplateFunc("gover", runtime.Version)
|
geth.SetTemplateFunc("gover", runtime.Version)
|
||||||
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
||||||
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
geth.SetTemplateFunc("niltime", func() string {
|
||||||
|
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||||
|
})
|
||||||
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||||
|
|
||||||
// Verify the actual welcome message to the required template
|
// Verify the actual welcome message to the required template
|
||||||
@ -142,7 +144,9 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
|||||||
attach.SetTemplateFunc("gover", runtime.Version)
|
attach.SetTemplateFunc("gover", runtime.Version)
|
||||||
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
||||||
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
attach.SetTemplateFunc("niltime", func() string {
|
||||||
|
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||||
|
})
|
||||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||||
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
||||||
attach.SetTemplateFunc("apis", func() string { return apis })
|
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||||
|
@ -20,14 +20,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/jsre"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// bridge is a collection of JavaScript utility methods to bride the .js runtime
|
// bridge is a collection of JavaScript utility methods to bride the .js runtime
|
||||||
@ -47,10 +49,18 @@ func newBridge(client *rpc.Client, prompter UserPrompter, printer io.Writer) *br
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
// 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.
|
// RPC method (saved in jeth.newAccount) with it to actually execute the RPC call.
|
||||||
func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) {
|
||||||
var (
|
var (
|
||||||
password string
|
password string
|
||||||
confirm string
|
confirm string
|
||||||
@ -58,52 +68,57 @@ func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
|||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
// No password was specified, prompt the user for it
|
// No password was specified, prompt the user for it
|
||||||
case len(call.ArgumentList) == 0:
|
case len(call.Arguments) == 0:
|
||||||
if password, err = b.prompter.PromptPassword("Password: "); err != nil {
|
if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
if confirm, err = b.prompter.PromptPassword("Repeat password: "); err != nil {
|
if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
if password != confirm {
|
if password != confirm {
|
||||||
throwJSException("passwords don't match!")
|
return nil, fmt.Errorf("passwords don't match!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single string password was specified, use that
|
// A single string password was specified, use that
|
||||||
case len(call.ArgumentList) == 1 && call.Argument(0).IsString():
|
case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil:
|
||||||
password, _ = call.Argument(0).ToString()
|
password = call.Argument(0).ToString().String()
|
||||||
|
|
||||||
// Otherwise fail with some error
|
|
||||||
default:
|
default:
|
||||||
throwJSException("expected 0 or 1 string argument")
|
return nil, fmt.Errorf("expected 0 or 1 string argument")
|
||||||
}
|
}
|
||||||
// Password acquired, execute the call and return
|
// Password acquired, execute the call and return
|
||||||
ret, err := call.Otto.Call("jeth.newAccount", nil, password)
|
newAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("newAccount"))
|
||||||
if err != nil {
|
if !callable {
|
||||||
throwJSException(err.Error())
|
return nil, fmt.Errorf("jeth.newAccount is not callable")
|
||||||
}
|
}
|
||||||
return ret
|
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
|
// OpenWallet is a wrapper around personal.openWallet which can interpret and
|
||||||
// react to certain error messages, such as the Trezor PIN matrix request.
|
// react to certain error messages, such as the Trezor PIN matrix request.
|
||||||
func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) {
|
||||||
// Make sure we have a wallet specified to open
|
// Make sure we have a wallet specified to open
|
||||||
if !call.Argument(0).IsString() {
|
if call.Argument(0).ToObject(call.VM).ClassName() != "String" {
|
||||||
throwJSException("first argument must be the wallet URL to open")
|
return nil, fmt.Errorf("first argument must be the wallet URL to open")
|
||||||
}
|
}
|
||||||
wallet := call.Argument(0)
|
wallet := call.Argument(0)
|
||||||
|
|
||||||
var passwd otto.Value
|
var passwd goja.Value
|
||||||
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) {
|
||||||
passwd, _ = otto.ToValue("")
|
passwd = call.VM.ToValue("")
|
||||||
} else {
|
} else {
|
||||||
passwd = call.Argument(1)
|
passwd = call.Argument(1)
|
||||||
}
|
}
|
||||||
// Open the wallet and return if successful in itself
|
// Open the wallet and return if successful in itself
|
||||||
val, err := call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
|
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
||||||
|
if !callable {
|
||||||
|
return nil, fmt.Errorf("jeth.openWallet is not callable")
|
||||||
|
}
|
||||||
|
val, err := openWallet(goja.Null(), wallet, passwd)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return val
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wallet open failed, report error unless it's a PIN or PUK entry
|
// Wallet open failed, report error unless it's a PIN or PUK entry
|
||||||
@ -111,32 +126,31 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
|
|||||||
case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()):
|
case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()):
|
||||||
val, err = b.readPinAndReopenWallet(call)
|
val, err = b.readPinAndReopenWallet(call)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return val
|
return val, nil
|
||||||
}
|
}
|
||||||
val, err = b.readPassphraseAndReopenWallet(call)
|
val, err = b.readPassphraseAndReopenWallet(call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()):
|
case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()):
|
||||||
// PUK input requested, fetch from the user and call open again
|
// PUK input requested, fetch from the user and call open again
|
||||||
if input, err := b.prompter.PromptPassword("Please enter the pairing password: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter the pairing password: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
passwd = call.VM.ToValue(input)
|
||||||
|
if val, err = openWallet(goja.Null(), wallet, passwd); err != nil {
|
||||||
if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) {
|
if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
// PIN input requested, fetch from the user and call open again
|
// PIN input requested, fetch from the user and call open again
|
||||||
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter current PIN: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,52 +158,52 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
|
|||||||
case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()):
|
case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()):
|
||||||
// PIN unblock requested, fetch PUK and new PIN from the user
|
// PIN unblock requested, fetch PUK and new PIN from the user
|
||||||
var pukpin string
|
var pukpin string
|
||||||
if input, err := b.prompter.PromptPassword("Please enter current PUK: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter current PUK: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
|
}
|
||||||
pukpin = input
|
pukpin = input
|
||||||
|
input, err = b.prompter.PromptPassword("Please enter new PIN: ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if input, err := b.prompter.PromptPassword("Please enter new PIN: "); err != nil {
|
|
||||||
throwJSException(err.Error())
|
|
||||||
} else {
|
|
||||||
pukpin += input
|
pukpin += input
|
||||||
}
|
|
||||||
passwd, _ = otto.ToValue(pukpin)
|
if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(pukpin)); err != nil {
|
||||||
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
return nil, err
|
||||||
throwJSException(err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()):
|
case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()):
|
||||||
// PIN input requested, fetch from the user and call open again
|
// PIN input requested, fetch from the user and call open again
|
||||||
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter current PIN: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Unknown error occurred, drop to the user
|
// Unknown error occurred, drop to the user
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
return val
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridge) readPassphraseAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
|
func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, error) {
|
||||||
var passwd otto.Value
|
|
||||||
wallet := call.Argument(0)
|
wallet := call.Argument(0)
|
||||||
if input, err := b.prompter.PromptPassword("Please enter your password: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter your passphrase: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
|
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
||||||
|
if !callable {
|
||||||
|
return nil, fmt.Errorf("jeth.openWallet is not callable")
|
||||||
|
}
|
||||||
|
return openWallet(goja.Null(), wallet, call.VM.ToValue(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridge) readPinAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
|
func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) {
|
||||||
var passwd otto.Value
|
|
||||||
wallet := call.Argument(0)
|
wallet := call.Argument(0)
|
||||||
// Trezor PIN matrix input requested, display the matrix to the user and fetch the data
|
// 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, "Look at the device for number positions\n\n")
|
||||||
@ -199,155 +213,154 @@ func (b *bridge) readPinAndReopenWallet(call otto.FunctionCall) (otto.Value, err
|
|||||||
fmt.Fprintf(b.printer, "--+---+--\n")
|
fmt.Fprintf(b.printer, "--+---+--\n")
|
||||||
fmt.Fprintf(b.printer, "1 | 2 | 3\n\n")
|
fmt.Fprintf(b.printer, "1 | 2 | 3\n\n")
|
||||||
|
|
||||||
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
input, err := b.prompter.PromptPassword("Please enter current PIN: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
|
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
||||||
|
if !callable {
|
||||||
|
return nil, fmt.Errorf("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
|
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that
|
||||||
// uses a non-echoing password prompt to acquire the passphrase and executes the
|
// 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
|
// original RPC method (saved in jeth.unlockAccount) with it to actually execute
|
||||||
// the RPC call.
|
// the RPC call.
|
||||||
func (b *bridge) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) {
|
||||||
// Make sure we have an account specified to unlock
|
// Make sure we have an account specified to unlock.
|
||||||
if !call.Argument(0).IsString() {
|
if call.Argument(0).ExportType().Kind() != reflect.String {
|
||||||
throwJSException("first argument must be the account to unlock")
|
return nil, fmt.Errorf("first argument must be the account to unlock")
|
||||||
}
|
}
|
||||||
account := call.Argument(0)
|
account := call.Argument(0)
|
||||||
|
|
||||||
// If password is not given or is the null value, prompt the user for it
|
// If password is not given or is the null value, prompt the user for it.
|
||||||
var passwd otto.Value
|
var passwd goja.Value
|
||||||
|
if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) {
|
||||||
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
|
||||||
fmt.Fprintf(b.printer, "Unlock account %s\n", account)
|
fmt.Fprintf(b.printer, "Unlock account %s\n", account)
|
||||||
if input, err := b.prompter.PromptPassword("Password: "); err != nil {
|
input, err := b.prompter.PromptPassword("Passphrase: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
|
passwd = call.VM.ToValue(input)
|
||||||
} else {
|
} else {
|
||||||
if !call.Argument(1).IsString() {
|
if call.Argument(1).ExportType().Kind() != reflect.String {
|
||||||
throwJSException("password must be a string")
|
return nil, fmt.Errorf("password must be a string")
|
||||||
}
|
}
|
||||||
passwd = call.Argument(1)
|
passwd = call.Argument(1)
|
||||||
}
|
}
|
||||||
// Third argument is the duration how long the account must be unlocked.
|
|
||||||
duration := otto.NullValue()
|
// Third argument is the duration how long the account should be unlocked.
|
||||||
if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() {
|
duration := goja.Null()
|
||||||
if !call.Argument(2).IsNumber() {
|
if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) {
|
||||||
throwJSException("unlock duration must be a number")
|
if !isNumber(call.Argument(2)) {
|
||||||
|
return nil, fmt.Errorf("unlock duration must be a number")
|
||||||
}
|
}
|
||||||
duration = call.Argument(2)
|
duration = call.Argument(2)
|
||||||
}
|
}
|
||||||
// Send the request to the backend and return
|
|
||||||
val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration)
|
// Send the request to the backend and return.
|
||||||
if err != nil {
|
unlockAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
|
||||||
throwJSException(err.Error())
|
if !callable {
|
||||||
|
return nil, fmt.Errorf("jeth.unlockAccount is not callable")
|
||||||
}
|
}
|
||||||
return val
|
return unlockAccount(goja.Null(), account, passwd, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password
|
// 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
|
// prompt to acquire the passphrase and executes the original RPC method (saved in
|
||||||
// jeth.sign) with it to actually execute the RPC call.
|
// jeth.sign) with it to actually execute the RPC call.
|
||||||
func (b *bridge) Sign(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) Sign(call jsre.Call) (goja.Value, error) {
|
||||||
var (
|
var (
|
||||||
message = call.Argument(0)
|
message = call.Argument(0)
|
||||||
account = call.Argument(1)
|
account = call.Argument(1)
|
||||||
passwd = call.Argument(2)
|
passwd = call.Argument(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !message.IsString() {
|
if message.ExportType().Kind() != reflect.String {
|
||||||
throwJSException("first argument must be the message to sign")
|
return nil, fmt.Errorf("first argument must be the message to sign")
|
||||||
}
|
}
|
||||||
if !account.IsString() {
|
if account.ExportType().Kind() != reflect.String {
|
||||||
throwJSException("second argument must be the account to sign with")
|
return nil, fmt.Errorf("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 the password is not given or null ask the user and ensure password is a string
|
||||||
if passwd.IsUndefined() || passwd.IsNull() {
|
if goja.IsUndefined(passwd) || goja.IsNull(passwd) {
|
||||||
fmt.Fprintf(b.printer, "Give password for account %s\n", account)
|
fmt.Fprintf(b.printer, "Give password for account %s\n", account)
|
||||||
if input, err := b.prompter.PromptPassword("Password: "); err != nil {
|
input, err := b.prompter.PromptPassword("Password: ")
|
||||||
throwJSException(err.Error())
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
passwd, _ = otto.ToValue(input)
|
|
||||||
}
|
}
|
||||||
}
|
passwd = call.VM.ToValue(input)
|
||||||
if !passwd.IsString() {
|
} else if passwd.ExportType().Kind() != reflect.String {
|
||||||
throwJSException("third argument must be the password to unlock the account")
|
return nil, fmt.Errorf("third argument must be the password to unlock the account")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request to the backend and return
|
// Send the request to the backend and return
|
||||||
val, err := call.Otto.Call("jeth.sign", nil, message, account, passwd)
|
sign, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
|
||||||
if err != nil {
|
if !callable {
|
||||||
throwJSException(err.Error())
|
return nil, fmt.Errorf("jeth.unlockAccount is not callable")
|
||||||
}
|
}
|
||||||
return val
|
return sign(goja.Null(), message, account, passwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep will block the console for the specified number of seconds.
|
// Sleep will block the console for the specified number of seconds.
|
||||||
func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) {
|
||||||
if call.Argument(0).IsNumber() {
|
if !isNumber(call.Argument(0)) {
|
||||||
sleep, _ := call.Argument(0).ToInteger()
|
return nil, fmt.Errorf("usage: sleep(<number of seconds>)")
|
||||||
time.Sleep(time.Duration(sleep) * time.Second)
|
|
||||||
return otto.TrueValue()
|
|
||||||
}
|
}
|
||||||
return throwJSException("usage: sleep(<number of seconds>)")
|
sleep := call.Argument(0).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
|
// SleepBlocks will block the console for a specified number of new blocks optionally
|
||||||
// until the given timeout is reached.
|
// until the given timeout is reached.
|
||||||
func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) {
|
||||||
|
// Parse the input parameters for the sleep.
|
||||||
var (
|
var (
|
||||||
blocks = int64(0)
|
blocks = int64(0)
|
||||||
sleep = int64(9999999999999999) // indefinitely
|
sleep = int64(9999999999999999) // indefinitely
|
||||||
)
|
)
|
||||||
// Parse the input parameters for the sleep
|
nArgs := len(call.Arguments)
|
||||||
nArgs := len(call.ArgumentList)
|
|
||||||
if nArgs == 0 {
|
if nArgs == 0 {
|
||||||
throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])")
|
return nil, fmt.Errorf("usage: sleepBlocks(<n blocks>[, max sleep in seconds])")
|
||||||
}
|
}
|
||||||
if nArgs >= 1 {
|
if nArgs >= 1 {
|
||||||
if call.Argument(0).IsNumber() {
|
if !isNumber(call.Argument(0)) {
|
||||||
blocks, _ = call.Argument(0).ToInteger()
|
return nil, fmt.Errorf("expected number as first argument")
|
||||||
} else {
|
|
||||||
throwJSException("expected number as first argument")
|
|
||||||
}
|
}
|
||||||
|
blocks = call.Argument(0).ToInteger()
|
||||||
}
|
}
|
||||||
if nArgs >= 2 {
|
if nArgs >= 2 {
|
||||||
if call.Argument(1).IsNumber() {
|
if isNumber(call.Argument(1)) {
|
||||||
sleep, _ = call.Argument(1).ToInteger()
|
return nil, fmt.Errorf("expected number as second argument")
|
||||||
} else {
|
|
||||||
throwJSException("expected number as second argument")
|
|
||||||
}
|
}
|
||||||
|
sleep = call.Argument(1).ToInteger()
|
||||||
}
|
}
|
||||||
// go through the console, this will allow web3 to call the appropriate
|
|
||||||
// callbacks if a delayed response or notification is received.
|
|
||||||
blockNumber := func() int64 {
|
|
||||||
result, err := call.Otto.Run("eth.blockNumber")
|
|
||||||
if err != nil {
|
|
||||||
throwJSException(err.Error())
|
|
||||||
}
|
|
||||||
block, err := result.ToInteger()
|
|
||||||
if err != nil {
|
|
||||||
throwJSException(err.Error())
|
|
||||||
}
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
// Poll the current block number until either it ot a timeout is reached
|
|
||||||
targetBlockNr := blockNumber() + blocks
|
|
||||||
deadline := time.Now().Add(time.Duration(sleep) * time.Second)
|
|
||||||
|
|
||||||
|
// Poll the current block number until either it or a timeout is reached.
|
||||||
|
var (
|
||||||
|
deadline = time.Now().Add(time.Duration(sleep) * time.Second)
|
||||||
|
lastNumber = ^hexutil.Uint64(0)
|
||||||
|
)
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
if blockNumber() >= targetBlockNr {
|
var number hexutil.Uint64
|
||||||
return otto.TrueValue()
|
err := b.client.Call(&number, "eth_blockNumber")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if number != lastNumber {
|
||||||
|
lastNumber = number
|
||||||
|
blocks--
|
||||||
|
}
|
||||||
|
if blocks <= 0 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
return otto.FalseValue()
|
return call.VM.ToValue(true), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonrpcCall struct {
|
type jsonrpcCall struct {
|
||||||
@ -357,15 +370,15 @@ type jsonrpcCall struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send implements the web3 provider "send" method.
|
// Send implements the web3 provider "send" method.
|
||||||
func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
|
func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
|
||||||
// Remarshal the request into a Go value.
|
// Remarshal the request into a Go value.
|
||||||
JSON, _ := call.Otto.Object("JSON")
|
reqVal, err := call.Argument(0).ToObject(call.VM).MarshalJSON()
|
||||||
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwJSException(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rawReq = reqVal.String()
|
rawReq = string(reqVal)
|
||||||
dec = json.NewDecoder(strings.NewReader(rawReq))
|
dec = json.NewDecoder(strings.NewReader(rawReq))
|
||||||
reqs []jsonrpcCall
|
reqs []jsonrpcCall
|
||||||
batch bool
|
batch bool
|
||||||
@ -381,10 +394,12 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute the requests.
|
// Execute the requests.
|
||||||
resps, _ := call.Otto.Object("new Array()")
|
var resps []*goja.Object
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
|
resp := call.VM.NewObject()
|
||||||
|
resp.Set("jsonrpc", "2.0")
|
||||||
resp.Set("id", req.ID)
|
resp.Set("id", req.ID)
|
||||||
|
|
||||||
var result json.RawMessage
|
var result json.RawMessage
|
||||||
err = b.client.Call(&result, req.Method, req.Params...)
|
err = b.client.Call(&result, req.Method, req.Params...)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
@ -392,9 +407,14 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
|
|||||||
if result == nil {
|
if result == nil {
|
||||||
// Special case null because it is decoded as an empty
|
// Special case null because it is decoded as an empty
|
||||||
// raw message for some reason.
|
// raw message for some reason.
|
||||||
resp.Set("result", otto.NullValue())
|
resp.Set("result", goja.Null())
|
||||||
} else {
|
} else {
|
||||||
resultVal, err := JSON.Call("parse", string(result))
|
JSON := call.VM.Get("JSON").ToObject(call.VM)
|
||||||
|
parse, callable := goja.AssertFunction(JSON.Get("parse"))
|
||||||
|
if !callable {
|
||||||
|
return nil, fmt.Errorf("JSON.parse is not a function")
|
||||||
|
}
|
||||||
|
resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setError(resp, -32603, err.Error())
|
setError(resp, -32603, err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -406,33 +426,38 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
|
|||||||
default:
|
default:
|
||||||
setError(resp, -32603, err.Error())
|
setError(resp, -32603, err.Error())
|
||||||
}
|
}
|
||||||
resps.Call("push", resp)
|
resps = append(resps, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the responses either to the callback (if supplied)
|
// Return the responses either to the callback (if supplied)
|
||||||
// or directly as the return value.
|
// or directly as the return value.
|
||||||
|
var result goja.Value
|
||||||
if batch {
|
if batch {
|
||||||
response = resps.Value()
|
result = call.VM.ToValue(resps)
|
||||||
} else {
|
} else {
|
||||||
response, _ = resps.Get("0")
|
result = resps[0]
|
||||||
}
|
}
|
||||||
if fn := call.Argument(1); fn.Class() == "Function" {
|
if fn, isFunc := goja.AssertFunction(call.Argument(1)); isFunc {
|
||||||
fn.Call(otto.NullValue(), otto.NullValue(), response)
|
fn(goja.Null(), goja.Null(), result)
|
||||||
return otto.UndefinedValue()
|
return goja.Undefined(), nil
|
||||||
}
|
}
|
||||||
return response
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setError(resp *otto.Object, code int, msg string) {
|
func setError(resp *goja.Object, code int, msg string) {
|
||||||
resp.Set("error", map[string]interface{}{"code": code, "message": msg})
|
resp.Set("error", map[string]interface{}{"code": code, "message": msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
// isNumber returns true if input value is a JS number.
|
||||||
// Go panic and throw msg as a JavaScript error.
|
func isNumber(v goja.Value) bool {
|
||||||
func throwJSException(msg interface{}) otto.Value {
|
k := v.ExportType().Kind()
|
||||||
val, err := otto.ToValue(msg)
|
return k >= reflect.Int && k <= reflect.Float64
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to serialize JavaScript exception", "exception", msg, "err", err)
|
|
||||||
}
|
}
|
||||||
panic(val)
|
|
||||||
|
func getObject(vm *goja.Runtime, name string) *goja.Object {
|
||||||
|
v := vm.Get(name)
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ToObject(vm)
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
"github.com/ethereum/go-ethereum/internal/jsre"
|
"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/internal/web3ext"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/peterh/liner"
|
"github.com/peterh/liner"
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -86,6 +87,7 @@ func New(config Config) (*Console, error) {
|
|||||||
if config.Printer == nil {
|
if config.Printer == nil {
|
||||||
config.Printer = colorable.NewColorableStdout()
|
config.Printer = colorable.NewColorableStdout()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the console and return
|
// Initialize the console and return
|
||||||
console := &Console{
|
console := &Console{
|
||||||
client: config.Client,
|
client: config.Client,
|
||||||
@ -107,110 +109,35 @@ func New(config Config) (*Console, error) {
|
|||||||
// init retrieves the available APIs from the remote RPC provider and initializes
|
// init retrieves the available APIs from the remote RPC provider and initializes
|
||||||
// the console's JavaScript namespaces based on the exposed modules.
|
// the console's JavaScript namespaces based on the exposed modules.
|
||||||
func (c *Console) init(preload []string) error {
|
func (c *Console) init(preload []string) error {
|
||||||
// Initialize the JavaScript <-> Go RPC bridge
|
c.initConsoleObject()
|
||||||
|
|
||||||
|
// Initialize the JavaScript <-> Go RPC bridge.
|
||||||
bridge := newBridge(c.client, c.prompter, c.printer)
|
bridge := newBridge(c.client, c.prompter, c.printer)
|
||||||
c.jsre.Set("jeth", struct{}{})
|
if err := c.initWeb3(bridge); err != nil {
|
||||||
|
|
||||||
jethObj, _ := c.jsre.Get("jeth")
|
|
||||||
jethObj.Object().Set("send", bridge.Send)
|
|
||||||
jethObj.Object().Set("sendAsync", bridge.Send)
|
|
||||||
|
|
||||||
consoleObj, _ := c.jsre.Get("console")
|
|
||||||
consoleObj.Object().Set("log", c.consoleOutput)
|
|
||||||
consoleObj.Object().Set("error", c.consoleOutput)
|
|
||||||
|
|
||||||
// Load all the internal utility JavaScript libraries
|
|
||||||
if err := c.jsre.Compile("bignumber.js", jsre.BignumberJs); err != nil {
|
|
||||||
return fmt.Errorf("bignumber.js: %v", err)
|
|
||||||
}
|
|
||||||
if err := c.jsre.Compile("web3.js", jsre.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)
|
|
||||||
}
|
|
||||||
if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil {
|
|
||||||
return fmt.Errorf("web3 provider: %v", err)
|
|
||||||
}
|
|
||||||
// Load the supported APIs into the JavaScript runtime environment
|
|
||||||
apis, err := c.client.SupportedModules()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("api modules: %v", err)
|
|
||||||
}
|
|
||||||
flatten := "var eth = web3.eth; var personal = web3.personal; "
|
|
||||||
for api := range apis {
|
|
||||||
if api == "web3" {
|
|
||||||
continue // manually mapped or ignore
|
|
||||||
}
|
|
||||||
if file, ok := web3ext.Modules[api]; ok {
|
|
||||||
// Load our extension for the module.
|
|
||||||
if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil {
|
|
||||||
return fmt.Errorf("%s.js: %v", api, err)
|
|
||||||
}
|
|
||||||
flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
|
|
||||||
} else if obj, err := c.jsre.Run("web3." + api); err == nil && obj.IsObject() {
|
|
||||||
// Enable web3.js built-in extension if available.
|
|
||||||
flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = c.jsre.Run(flatten); err != nil {
|
|
||||||
return fmt.Errorf("namespace flattening: %v", err)
|
|
||||||
}
|
|
||||||
// Initialize the global name register (disabled for now)
|
|
||||||
//c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
|
|
||||||
|
|
||||||
// If the console is in interactive mode, instrument password related methods to query the user
|
|
||||||
if c.prompter != nil {
|
|
||||||
// Retrieve the account management object to instrument
|
|
||||||
personal, err := c.jsre.Get("personal")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Override the openWallet, unlockAccount, newAccount and sign methods since
|
if err := c.initExtensions(); err != nil {
|
||||||
// these require user interaction. Assign these method in the Console the
|
|
||||||
// original web3 callbacks. These will be called by the jeth.* methods after
|
|
||||||
// they got the password from the user and send the original web3 request to
|
|
||||||
// the backend.
|
|
||||||
if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
|
|
||||||
if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
|
|
||||||
return fmt.Errorf("personal.openWallet: %v", err)
|
|
||||||
}
|
|
||||||
if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
|
|
||||||
return fmt.Errorf("personal.unlockAccount: %v", err)
|
|
||||||
}
|
|
||||||
if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
|
|
||||||
return fmt.Errorf("personal.newAccount: %v", err)
|
|
||||||
}
|
|
||||||
if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
|
|
||||||
return fmt.Errorf("personal.sign: %v", err)
|
|
||||||
}
|
|
||||||
obj.Set("openWallet", bridge.OpenWallet)
|
|
||||||
obj.Set("unlockAccount", bridge.UnlockAccount)
|
|
||||||
obj.Set("newAccount", bridge.NewAccount)
|
|
||||||
obj.Set("sign", bridge.Sign)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
|
|
||||||
admin, err := c.jsre.Get("admin")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if obj := admin.Object(); obj != nil { // make sure the admin api is enabled over the interface
|
|
||||||
obj.Set("sleepBlocks", bridge.SleepBlocks)
|
// Add bridge overrides for web3.js functionality.
|
||||||
obj.Set("sleep", bridge.Sleep)
|
c.jsre.Do(func(vm *goja.Runtime) {
|
||||||
obj.Set("clearHistory", c.clearHistory)
|
c.initAdmin(vm, bridge)
|
||||||
}
|
c.initPersonal(vm, bridge)
|
||||||
// Preload any JavaScript files before starting the console
|
})
|
||||||
|
|
||||||
|
// Preload JavaScript files.
|
||||||
for _, path := range preload {
|
for _, path := range preload {
|
||||||
if err := c.jsre.Exec(path); err != nil {
|
if err := c.jsre.Exec(path); err != nil {
|
||||||
failure := err.Error()
|
failure := err.Error()
|
||||||
if ottoErr, ok := err.(*otto.Error); ok {
|
if gojaErr, ok := err.(*goja.Exception); ok {
|
||||||
failure = ottoErr.String()
|
failure = gojaErr.String()
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %v", path, failure)
|
return fmt.Errorf("%s: %v", path, failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Configure the console's input prompter for scrollback and tab completion
|
|
||||||
|
// Configure the input prompter for history and tab completion.
|
||||||
if c.prompter != nil {
|
if c.prompter != nil {
|
||||||
if content, err := ioutil.ReadFile(c.histPath); err != nil {
|
if content, err := ioutil.ReadFile(c.histPath); err != nil {
|
||||||
c.prompter.SetHistory(nil)
|
c.prompter.SetHistory(nil)
|
||||||
@ -223,6 +150,102 @@ func (c *Console) init(preload []string) error {
|
|||||||
return nil
|
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 {
|
||||||
|
bnJS := string(deps.MustAsset("bignumber.js"))
|
||||||
|
web3JS := string(deps.MustAsset("web3.js"))
|
||||||
|
if err := c.jsre.Compile("bignumber.js", bnJS); err != nil {
|
||||||
|
return fmt.Errorf("bignumber.js: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.jsre.Compile("web3.js", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// initExtensions loads and registers web3.js extensions.
|
||||||
|
func (c *Console) initExtensions() error {
|
||||||
|
// Compute aliases from server-provided modules.
|
||||||
|
apis, err := c.client.SupportedModules()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("api modules: %v", err)
|
||||||
|
}
|
||||||
|
aliases := map[string]struct{}{"eth": {}, "personal": {}}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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() {
|
func (c *Console) clearHistory() {
|
||||||
c.history = nil
|
c.history = nil
|
||||||
c.prompter.ClearHistory()
|
c.prompter.ClearHistory()
|
||||||
@ -235,13 +258,13 @@ func (c *Console) clearHistory() {
|
|||||||
|
|
||||||
// consoleOutput is an override for the console.log and console.error methods to
|
// consoleOutput is an override for the console.log and console.error methods to
|
||||||
// stream the output into the configured output stream instead of stdout.
|
// stream the output into the configured output stream instead of stdout.
|
||||||
func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
|
func (c *Console) consoleOutput(call goja.FunctionCall) goja.Value {
|
||||||
var output []string
|
var output []string
|
||||||
for _, argument := range call.ArgumentList {
|
for _, argument := range call.Arguments {
|
||||||
output = append(output, fmt.Sprintf("%v", argument))
|
output = append(output, fmt.Sprintf("%v", argument))
|
||||||
}
|
}
|
||||||
fmt.Fprintln(c.printer, strings.Join(output, " "))
|
fmt.Fprintln(c.printer, strings.Join(output, " "))
|
||||||
return otto.Value{}
|
return goja.Null()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoCompleteInput is a pre-assembled word completer to be used by the user
|
// AutoCompleteInput is a pre-assembled word completer to be used by the user
|
||||||
@ -304,13 +327,13 @@ func (c *Console) Welcome() {
|
|||||||
|
|
||||||
// Evaluate executes code and pretty prints the result to the specified output
|
// Evaluate executes code and pretty prints the result to the specified output
|
||||||
// stream.
|
// stream.
|
||||||
func (c *Console) Evaluate(statement string) error {
|
func (c *Console) Evaluate(statement string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
fmt.Fprintf(c.printer, "[native] error: %v\n", r)
|
fmt.Fprintf(c.printer, "[native] error: %v\n", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return c.jsre.Evaluate(statement, c.printer)
|
c.jsre.Evaluate(statement, c.printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interactive starts an interactive user session, where input is propted from
|
// Interactive starts an interactive user session, where input is propted from
|
||||||
|
@ -289,7 +289,7 @@ func TestPrettyError(t *testing.T) {
|
|||||||
defer tester.Close(t)
|
defer tester.Close(t)
|
||||||
tester.console.Evaluate("throw 'hello'")
|
tester.console.Evaluate("throw 'hello'")
|
||||||
|
|
||||||
want := jsre.ErrorColor("hello") + "\n"
|
want := jsre.ErrorColor("hello") + "\n\tat <eval>:1:7(1)\n\n"
|
||||||
if output := tester.output.String(); output != want {
|
if output := tester.output.String(); output != want {
|
||||||
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
|
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
|
||||||
}
|
}
|
||||||
|
3
go.mod
3
go.mod
@ -16,13 +16,16 @@ require (
|
|||||||
github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9
|
github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
|
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
|
||||||
|
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
|
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
|
||||||
|
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c
|
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c
|
||||||
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa
|
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa
|
||||||
github.com/fatih/color v1.3.0
|
github.com/fatih/color v1.3.0
|
||||||
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
|
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
|
||||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect
|
||||||
github.com/go-stack/stack v1.8.0
|
github.com/go-stack/stack v1.8.0
|
||||||
github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c
|
github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c
|
||||||
github.com/golang/snappy v0.0.1
|
github.com/golang/snappy v0.0.1
|
||||||
|
8
go.sum
8
go.sum
@ -57,8 +57,14 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs
|
|||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
|
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
|
||||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/dop251/goja v0.0.0-20191203121440-007eef3bc40f h1:vtCDQseO/Sbu5IZSoc2uzZ7CkSoai7OtpcwGFK5FlyE=
|
||||||
|
github.com/dop251/goja v0.0.0-20191203121440-007eef3bc40f/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
|
||||||
|
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29 h1:Ewd9K+mC725sITA12QQHRqWj78NU4t7EhlFVVgdlzJg=
|
||||||
|
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM=
|
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY=
|
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY=
|
||||||
@ -77,6 +83,8 @@ github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2i
|
|||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
||||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
@ -20,35 +20,43 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/dop251/goja"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompleteKeywords returns potential continuations for the given line. Since line is
|
// CompleteKeywords returns potential continuations for the given line. Since line is
|
||||||
// evaluated, callers need to make sure that evaluating line does not have side effects.
|
// evaluated, callers need to make sure that evaluating line does not have side effects.
|
||||||
func (jsre *JSRE) CompleteKeywords(line string) []string {
|
func (jsre *JSRE) CompleteKeywords(line string) []string {
|
||||||
var results []string
|
var results []string
|
||||||
jsre.Do(func(vm *otto.Otto) {
|
jsre.Do(func(vm *goja.Runtime) {
|
||||||
results = getCompletions(vm, line)
|
results = getCompletions(vm, line)
|
||||||
})
|
})
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompletions(vm *otto.Otto, line string) (results []string) {
|
func getCompletions(vm *goja.Runtime, line string) (results []string) {
|
||||||
parts := strings.Split(line, ".")
|
parts := strings.Split(line, ".")
|
||||||
objRef := "this"
|
if len(parts) == 0 {
|
||||||
prefix := line
|
|
||||||
if len(parts) > 1 {
|
|
||||||
objRef = strings.Join(parts[0:len(parts)-1], ".")
|
|
||||||
prefix = parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, _ := vm.Object(objRef)
|
|
||||||
if obj == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the right-most fully named object in the line. e.g. if line = "x.y.z"
|
||||||
|
// and "x.y" is an object, obj will reference "x.y".
|
||||||
|
obj := vm.GlobalObject()
|
||||||
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
|
v := obj.Get(parts[i])
|
||||||
|
if v == nil {
|
||||||
|
return nil // No object was found
|
||||||
|
}
|
||||||
|
obj = v.ToObject(vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go over the keys of the object and retain the keys matching prefix.
|
||||||
|
// Example: if line = "x.y.z" and "x.y" exists and has keys "zebu", "zebra"
|
||||||
|
// and "platypus", then "x.y.zebu" and "x.y.zebra" will be added to results.
|
||||||
|
prefix := parts[len(parts)-1]
|
||||||
iterOwnAndConstructorKeys(vm, obj, func(k string) {
|
iterOwnAndConstructorKeys(vm, obj, func(k string) {
|
||||||
if strings.HasPrefix(k, prefix) {
|
if strings.HasPrefix(k, prefix) {
|
||||||
if objRef == "this" {
|
if len(parts) == 1 {
|
||||||
results = append(results, k)
|
results = append(results, k)
|
||||||
} else {
|
} else {
|
||||||
results = append(results, strings.Join(parts[:len(parts)-1], ".")+"."+k)
|
results = append(results, strings.Join(parts[:len(parts)-1], ".")+"."+k)
|
||||||
@ -59,9 +67,9 @@ func getCompletions(vm *otto.Otto, line string) (results []string) {
|
|||||||
// Append opening parenthesis (for functions) or dot (for objects)
|
// Append opening parenthesis (for functions) or dot (for objects)
|
||||||
// if the line itself is the only completion.
|
// if the line itself is the only completion.
|
||||||
if len(results) == 1 && results[0] == line {
|
if len(results) == 1 && results[0] == line {
|
||||||
obj, _ := vm.Object(line)
|
obj := obj.Get(parts[len(parts)-1])
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
if obj.Class() == "Function" {
|
if _, isfunc := goja.AssertFunction(obj); isfunc {
|
||||||
results[0] += "("
|
results[0] += "("
|
||||||
} else {
|
} else {
|
||||||
results[0] += "."
|
results[0] += "."
|
||||||
|
@ -39,6 +39,10 @@ func TestCompleteKeywords(t *testing.T) {
|
|||||||
input string
|
input string
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
input: "St",
|
||||||
|
want: []string{"String"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: "x",
|
input: "x",
|
||||||
want: []string{"x."},
|
want: []string{"x."},
|
||||||
|
@ -26,30 +26,30 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/internal/jsre/deps"
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// JSRE is a JS runtime environment embedding the goja interpreter.
|
||||||
BignumberJs = deps.MustAsset("bignumber.js")
|
// It provides helper functions to load code from files, run code snippets
|
||||||
Web3Js = deps.MustAsset("web3.js")
|
// and bind native go objects to JS.
|
||||||
)
|
//
|
||||||
|
// The runtime runs all code on a dedicated event loop and does not expose the underlying
|
||||||
/*
|
// goja runtime directly. To use the runtime, call JSRE.Do. When binding a Go function,
|
||||||
JSRE is a generic JS runtime environment embedding the otto JS interpreter.
|
// use the Call type to gain access to the runtime.
|
||||||
It provides some helper functions to
|
|
||||||
- load code from files
|
|
||||||
- run code snippets
|
|
||||||
- require libraries
|
|
||||||
- bind native go objects
|
|
||||||
*/
|
|
||||||
type JSRE struct {
|
type JSRE struct {
|
||||||
assetPath string
|
assetPath string
|
||||||
output io.Writer
|
output io.Writer
|
||||||
evalQueue chan *evalReq
|
evalQueue chan *evalReq
|
||||||
stopEventLoop chan bool
|
stopEventLoop chan bool
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
|
vm *goja.Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call is the argument type of Go functions which are callable from JS.
|
||||||
|
type Call struct {
|
||||||
|
goja.FunctionCall
|
||||||
|
VM *goja.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsTimer is a single timer instance with a callback function
|
// jsTimer is a single timer instance with a callback function
|
||||||
@ -57,12 +57,12 @@ type jsTimer struct {
|
|||||||
timer *time.Timer
|
timer *time.Timer
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
interval bool
|
interval bool
|
||||||
call otto.FunctionCall
|
call goja.FunctionCall
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalReq is a serialized vm execution request processed by runEventLoop.
|
// evalReq is a serialized vm execution request processed by runEventLoop.
|
||||||
type evalReq struct {
|
type evalReq struct {
|
||||||
fn func(vm *otto.Otto)
|
fn func(vm *goja.Runtime)
|
||||||
done chan bool
|
done chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +74,10 @@ func New(assetPath string, output io.Writer) *JSRE {
|
|||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
evalQueue: make(chan *evalReq),
|
evalQueue: make(chan *evalReq),
|
||||||
stopEventLoop: make(chan bool),
|
stopEventLoop: make(chan bool),
|
||||||
|
vm: goja.New(),
|
||||||
}
|
}
|
||||||
go re.runEventLoop()
|
go re.runEventLoop()
|
||||||
re.Set("loadScript", re.loadScript)
|
re.Set("loadScript", MakeCallback(re.vm, re.loadScript))
|
||||||
re.Set("inspect", re.prettyPrintJS)
|
re.Set("inspect", re.prettyPrintJS)
|
||||||
return re
|
return re
|
||||||
}
|
}
|
||||||
@ -99,21 +100,20 @@ func randomSource() *rand.Rand {
|
|||||||
// serialized way and calls timer callback functions at the appropriate time.
|
// serialized way and calls timer callback functions at the appropriate time.
|
||||||
|
|
||||||
// Exported functions always access the vm through the event queue. You can
|
// Exported functions always access the vm through the event queue. You can
|
||||||
// call the functions of the otto vm directly to circumvent the queue. These
|
// call the functions of the goja vm directly to circumvent the queue. These
|
||||||
// functions should be used if and only if running a routine that was already
|
// functions should be used if and only if running a routine that was already
|
||||||
// called from JS through an RPC call.
|
// called from JS through an RPC call.
|
||||||
func (re *JSRE) runEventLoop() {
|
func (re *JSRE) runEventLoop() {
|
||||||
defer close(re.closed)
|
defer close(re.closed)
|
||||||
|
|
||||||
vm := otto.New()
|
|
||||||
r := randomSource()
|
r := randomSource()
|
||||||
vm.SetRandomSource(r.Float64)
|
re.vm.SetRandSource(r.Float64)
|
||||||
|
|
||||||
registry := map[*jsTimer]*jsTimer{}
|
registry := map[*jsTimer]*jsTimer{}
|
||||||
ready := make(chan *jsTimer)
|
ready := make(chan *jsTimer)
|
||||||
|
|
||||||
newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
|
newTimer := func(call goja.FunctionCall, interval bool) (*jsTimer, goja.Value) {
|
||||||
delay, _ := call.Argument(1).ToInteger()
|
delay := call.Argument(1).ToInteger()
|
||||||
if 0 >= delay {
|
if 0 >= delay {
|
||||||
delay = 1
|
delay = 1
|
||||||
}
|
}
|
||||||
@ -128,47 +128,43 @@ func (re *JSRE) runEventLoop() {
|
|||||||
ready <- timer
|
ready <- timer
|
||||||
})
|
})
|
||||||
|
|
||||||
value, err := call.Otto.ToValue(timer)
|
return timer, re.vm.ToValue(timer)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return timer, value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout := func(call otto.FunctionCall) otto.Value {
|
setTimeout := func(call goja.FunctionCall) goja.Value {
|
||||||
_, value := newTimer(call, false)
|
_, value := newTimer(call, false)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval := func(call otto.FunctionCall) otto.Value {
|
setInterval := func(call goja.FunctionCall) goja.Value {
|
||||||
_, value := newTimer(call, true)
|
_, value := newTimer(call, true)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout := func(call otto.FunctionCall) otto.Value {
|
clearTimeout := func(call goja.FunctionCall) goja.Value {
|
||||||
timer, _ := call.Argument(0).Export()
|
timer := call.Argument(0).Export()
|
||||||
if timer, ok := timer.(*jsTimer); ok {
|
if timer, ok := timer.(*jsTimer); ok {
|
||||||
timer.timer.Stop()
|
timer.timer.Stop()
|
||||||
delete(registry, timer)
|
delete(registry, timer)
|
||||||
}
|
}
|
||||||
return otto.UndefinedValue()
|
return goja.Undefined()
|
||||||
}
|
}
|
||||||
vm.Set("_setTimeout", setTimeout)
|
re.vm.Set("_setTimeout", setTimeout)
|
||||||
vm.Set("_setInterval", setInterval)
|
re.vm.Set("_setInterval", setInterval)
|
||||||
vm.Run(`var setTimeout = function(args) {
|
re.vm.RunString(`var setTimeout = function(args) {
|
||||||
if (arguments.length < 1) {
|
if (arguments.length < 1) {
|
||||||
throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present.");
|
throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present.");
|
||||||
}
|
}
|
||||||
return _setTimeout.apply(this, arguments);
|
return _setTimeout.apply(this, arguments);
|
||||||
}`)
|
}`)
|
||||||
vm.Run(`var setInterval = function(args) {
|
re.vm.RunString(`var setInterval = function(args) {
|
||||||
if (arguments.length < 1) {
|
if (arguments.length < 1) {
|
||||||
throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present.");
|
throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present.");
|
||||||
}
|
}
|
||||||
return _setInterval.apply(this, arguments);
|
return _setInterval.apply(this, arguments);
|
||||||
}`)
|
}`)
|
||||||
vm.Set("clearTimeout", clearTimeout)
|
re.vm.Set("clearTimeout", clearTimeout)
|
||||||
vm.Set("clearInterval", clearTimeout)
|
re.vm.Set("clearInterval", clearTimeout)
|
||||||
|
|
||||||
var waitForCallbacks bool
|
var waitForCallbacks bool
|
||||||
|
|
||||||
@ -178,8 +174,8 @@ loop:
|
|||||||
case timer := <-ready:
|
case timer := <-ready:
|
||||||
// execute callback, remove/reschedule the timer
|
// execute callback, remove/reschedule the timer
|
||||||
var arguments []interface{}
|
var arguments []interface{}
|
||||||
if len(timer.call.ArgumentList) > 2 {
|
if len(timer.call.Arguments) > 2 {
|
||||||
tmp := timer.call.ArgumentList[2:]
|
tmp := timer.call.Arguments[2:]
|
||||||
arguments = make([]interface{}, 2+len(tmp))
|
arguments = make([]interface{}, 2+len(tmp))
|
||||||
for i, value := range tmp {
|
for i, value := range tmp {
|
||||||
arguments[i+2] = value
|
arguments[i+2] = value
|
||||||
@ -187,11 +183,12 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
arguments = make([]interface{}, 1)
|
arguments = make([]interface{}, 1)
|
||||||
}
|
}
|
||||||
arguments[0] = timer.call.ArgumentList[0]
|
arguments[0] = timer.call.Arguments[0]
|
||||||
_, err := vm.Call(`Function.call.call`, nil, arguments...)
|
call, isFunc := goja.AssertFunction(timer.call.Arguments[0])
|
||||||
if err != nil {
|
if !isFunc {
|
||||||
fmt.Println("js error:", err, arguments)
|
panic(re.vm.ToValue("js error: timer/timeout callback is not a function"))
|
||||||
}
|
}
|
||||||
|
call(goja.Null(), timer.call.Arguments...)
|
||||||
|
|
||||||
_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
|
_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
|
||||||
if timer.interval && inreg {
|
if timer.interval && inreg {
|
||||||
@ -204,7 +201,7 @@ loop:
|
|||||||
}
|
}
|
||||||
case req := <-re.evalQueue:
|
case req := <-re.evalQueue:
|
||||||
// run the code, send the result back
|
// run the code, send the result back
|
||||||
req.fn(vm)
|
req.fn(re.vm)
|
||||||
close(req.done)
|
close(req.done)
|
||||||
if waitForCallbacks && (len(registry) == 0) {
|
if waitForCallbacks && (len(registry) == 0) {
|
||||||
break loop
|
break loop
|
||||||
@ -223,7 +220,7 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do executes the given function on the JS event loop.
|
// Do executes the given function on the JS event loop.
|
||||||
func (re *JSRE) Do(fn func(*otto.Otto)) {
|
func (re *JSRE) Do(fn func(*goja.Runtime)) {
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
req := &evalReq{fn, done}
|
req := &evalReq{fn, done}
|
||||||
re.evalQueue <- req
|
re.evalQueue <- req
|
||||||
@ -246,70 +243,36 @@ func (re *JSRE) Exec(file string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var script *otto.Script
|
return re.Compile(file, string(code))
|
||||||
re.Do(func(vm *otto.Otto) {
|
|
||||||
script, err = vm.Compile(file, code)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = vm.Run(script)
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind assigns value v to a variable in the JS environment
|
|
||||||
// This method is deprecated, use Set.
|
|
||||||
func (re *JSRE) Bind(name string, v interface{}) error {
|
|
||||||
return re.Set(name, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs a piece of JS code.
|
// Run runs a piece of JS code.
|
||||||
func (re *JSRE) Run(code string) (v otto.Value, err error) {
|
func (re *JSRE) Run(code string) (v goja.Value, err error) {
|
||||||
re.Do(func(vm *otto.Otto) { v, err = vm.Run(code) })
|
re.Do(func(vm *goja.Runtime) { v, err = vm.RunString(code) })
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the value of a variable in the JS environment.
|
|
||||||
func (re *JSRE) Get(ns string) (v otto.Value, err error) {
|
|
||||||
re.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
|
|
||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set assigns value v to a variable in the JS environment.
|
// Set assigns value v to a variable in the JS environment.
|
||||||
func (re *JSRE) Set(ns string, v interface{}) (err error) {
|
func (re *JSRE) Set(ns string, v interface{}) (err error) {
|
||||||
re.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
|
re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) })
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadScript executes a JS script from inside the currently executing JS code.
|
// MakeCallback turns the given function into a function that's callable by JS.
|
||||||
func (re *JSRE) loadScript(call otto.FunctionCall) otto.Value {
|
func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Value {
|
||||||
file, err := call.Argument(0).ToString()
|
return vm.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||||
|
result, err := fn(Call{call, vm})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: throw exception
|
panic(vm.NewGoError(err))
|
||||||
return otto.FalseValue()
|
|
||||||
}
|
}
|
||||||
file = common.AbsolutePath(re.assetPath, file)
|
return result
|
||||||
source, err := ioutil.ReadFile(file)
|
})
|
||||||
if err != nil {
|
|
||||||
// TODO: throw exception
|
|
||||||
return otto.FalseValue()
|
|
||||||
}
|
|
||||||
if _, err := compileAndRun(call.Otto, file, source); err != nil {
|
|
||||||
// TODO: throw exception
|
|
||||||
fmt.Println("err:", err)
|
|
||||||
return otto.FalseValue()
|
|
||||||
}
|
|
||||||
// TODO: return evaluation result
|
|
||||||
return otto.TrueValue()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate executes code and pretty prints the result to the specified output
|
// Evaluate executes code and pretty prints the result to the specified output stream.
|
||||||
// stream.
|
func (re *JSRE) Evaluate(code string, w io.Writer) {
|
||||||
func (re *JSRE) Evaluate(code string, w io.Writer) error {
|
re.Do(func(vm *goja.Runtime) {
|
||||||
var fail error
|
val, err := vm.RunString(code)
|
||||||
|
|
||||||
re.Do(func(vm *otto.Otto) {
|
|
||||||
val, err := vm.Run(code)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
prettyError(vm, err, w)
|
prettyError(vm, err, w)
|
||||||
} else {
|
} else {
|
||||||
@ -317,19 +280,33 @@ func (re *JSRE) Evaluate(code string, w io.Writer) error {
|
|||||||
}
|
}
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
})
|
})
|
||||||
return fail
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile compiles and then runs a piece of JS code.
|
// Compile compiles and then runs a piece of JS code.
|
||||||
func (re *JSRE) Compile(filename string, src interface{}) (err error) {
|
func (re *JSRE) Compile(filename string, src string) (err error) {
|
||||||
re.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
|
re.Do(func(vm *goja.Runtime) { _, err = compileAndRun(vm, filename, src) })
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
|
// loadScript loads and executes a JS file.
|
||||||
script, err := vm.Compile(filename, src)
|
func (re *JSRE) loadScript(call Call) (goja.Value, error) {
|
||||||
|
file := call.Argument(0).ToString().String()
|
||||||
|
file = common.AbsolutePath(re.assetPath, file)
|
||||||
|
source, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return otto.Value{}, err
|
return nil, fmt.Errorf("Could not read file %s: %v", file, err)
|
||||||
}
|
}
|
||||||
return vm.Run(script)
|
value, err := compileAndRun(re.vm, file, string(source))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error while compiling or running script: %v", err)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, error) {
|
||||||
|
script, err := goja.Compile(filename, src, false)
|
||||||
|
if err != nil {
|
||||||
|
return goja.Null(), err
|
||||||
|
}
|
||||||
|
return vm.RunProgram(script)
|
||||||
}
|
}
|
||||||
|
@ -20,25 +20,24 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/dop251/goja"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testNativeObjectBinding struct{}
|
type testNativeObjectBinding struct {
|
||||||
|
vm *goja.Runtime
|
||||||
|
}
|
||||||
|
|
||||||
type msg struct {
|
type msg struct {
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value {
|
func (no *testNativeObjectBinding) TestMethod(call goja.FunctionCall) goja.Value {
|
||||||
m, err := call.Argument(0).ToString()
|
m := call.Argument(0).ToString().String()
|
||||||
if err != nil {
|
return no.vm.ToValue(&msg{m})
|
||||||
return otto.UndefinedValue()
|
|
||||||
}
|
|
||||||
v, _ := call.Otto.ToValue(&msg{m})
|
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
|
func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
|
||||||
@ -51,7 +50,8 @@ func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
|
|||||||
t.Fatal("cannot create test.js:", err)
|
t.Fatal("cannot create test.js:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return New(dir, os.Stdout), dir
|
jsre := New(dir, os.Stdout)
|
||||||
|
return jsre, dir
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExec(t *testing.T) {
|
func TestExec(t *testing.T) {
|
||||||
@ -66,11 +66,11 @@ func TestExec(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
if !val.IsString() {
|
if val.ExportType().Kind() != reflect.String {
|
||||||
t.Errorf("expected string value, got %v", val)
|
t.Errorf("expected string value, got %v", val)
|
||||||
}
|
}
|
||||||
exp := "testMsg"
|
exp := "testMsg"
|
||||||
got, _ := val.ToString()
|
got := val.ToString().String()
|
||||||
if exp != got {
|
if exp != got {
|
||||||
t.Errorf("expected '%v', got '%v'", exp, got)
|
t.Errorf("expected '%v', got '%v'", exp, got)
|
||||||
}
|
}
|
||||||
@ -90,11 +90,11 @@ func TestNatto(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
if !val.IsString() {
|
if val.ExportType().Kind() != reflect.String {
|
||||||
t.Errorf("expected string value, got %v", val)
|
t.Errorf("expected string value, got %v", val)
|
||||||
}
|
}
|
||||||
exp := "testMsg"
|
exp := "testMsg"
|
||||||
got, _ := val.ToString()
|
got := val.ToString().String()
|
||||||
if exp != got {
|
if exp != got {
|
||||||
t.Errorf("expected '%v', got '%v'", exp, got)
|
t.Errorf("expected '%v', got '%v'", exp, got)
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ func TestBind(t *testing.T) {
|
|||||||
jsre := New("", os.Stdout)
|
jsre := New("", os.Stdout)
|
||||||
defer jsre.Stop(false)
|
defer jsre.Stop(false)
|
||||||
|
|
||||||
jsre.Bind("no", &testNativeObjectBinding{})
|
jsre.Set("no", &testNativeObjectBinding{vm: jsre.vm})
|
||||||
|
|
||||||
_, err := jsre.Run(`no.TestMethod("testMsg")`)
|
_, err := jsre.Run(`no.TestMethod("testMsg")`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,11 +125,11 @@ func TestLoadScript(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
if !val.IsString() {
|
if val.ExportType().Kind() != reflect.String {
|
||||||
t.Errorf("expected string value, got %v", val)
|
t.Errorf("expected string value, got %v", val)
|
||||||
}
|
}
|
||||||
exp := "testMsg"
|
exp := "testMsg"
|
||||||
got, _ := val.ToString()
|
got := val.ToString().String()
|
||||||
if exp != got {
|
if exp != got {
|
||||||
t.Errorf("expected '%v', got '%v'", exp, got)
|
t.Errorf("expected '%v', got '%v'", exp, got)
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,13 @@ package jsre
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -52,29 +53,29 @@ var boringKeys = map[string]bool{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prettyPrint writes value to standard output.
|
// prettyPrint writes value to standard output.
|
||||||
func prettyPrint(vm *otto.Otto, value otto.Value, w io.Writer) {
|
func prettyPrint(vm *goja.Runtime, value goja.Value, w io.Writer) {
|
||||||
ppctx{vm: vm, w: w}.printValue(value, 0, false)
|
ppctx{vm: vm, w: w}.printValue(value, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prettyError writes err to standard output.
|
// prettyError writes err to standard output.
|
||||||
func prettyError(vm *otto.Otto, err error, w io.Writer) {
|
func prettyError(vm *goja.Runtime, err error, w io.Writer) {
|
||||||
failure := err.Error()
|
failure := err.Error()
|
||||||
if ottoErr, ok := err.(*otto.Error); ok {
|
if gojaErr, ok := err.(*goja.Exception); ok {
|
||||||
failure = ottoErr.String()
|
failure = gojaErr.String()
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, ErrorColor("%s", failure))
|
fmt.Fprint(w, ErrorColor("%s", failure))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value {
|
func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value {
|
||||||
for _, v := range call.ArgumentList {
|
for _, v := range call.Arguments {
|
||||||
prettyPrint(call.Otto, v, re.output)
|
prettyPrint(re.vm, v, re.output)
|
||||||
fmt.Fprintln(re.output)
|
fmt.Fprintln(re.output)
|
||||||
}
|
}
|
||||||
return otto.UndefinedValue()
|
return goja.Undefined()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ppctx struct {
|
type ppctx struct {
|
||||||
vm *otto.Otto
|
vm *goja.Runtime
|
||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,35 +83,47 @@ func (ctx ppctx) indent(level int) string {
|
|||||||
return strings.Repeat(indentString, level)
|
return strings.Repeat(indentString, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) {
|
func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) {
|
||||||
|
if goja.IsNull(v) || goja.IsUndefined(v) {
|
||||||
|
fmt.Fprint(ctx.w, SpecialColor(v.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kind := v.ExportType().Kind()
|
||||||
switch {
|
switch {
|
||||||
case v.IsObject():
|
case kind == reflect.Bool:
|
||||||
ctx.printObject(v.Object(), level, inArray)
|
fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean()))
|
||||||
case v.IsNull():
|
case kind == reflect.String:
|
||||||
fmt.Fprint(ctx.w, SpecialColor("null"))
|
fmt.Fprint(ctx.w, StringColor("%q", v.String()))
|
||||||
case v.IsUndefined():
|
case kind >= reflect.Int && kind <= reflect.Complex128:
|
||||||
fmt.Fprint(ctx.w, SpecialColor("undefined"))
|
fmt.Fprint(ctx.w, NumberColor("%s", v.String()))
|
||||||
case v.IsString():
|
|
||||||
s, _ := v.ToString()
|
|
||||||
fmt.Fprint(ctx.w, StringColor("%q", s))
|
|
||||||
case v.IsBoolean():
|
|
||||||
b, _ := v.ToBoolean()
|
|
||||||
fmt.Fprint(ctx.w, SpecialColor("%t", b))
|
|
||||||
case v.IsNaN():
|
|
||||||
fmt.Fprint(ctx.w, NumberColor("NaN"))
|
|
||||||
case v.IsNumber():
|
|
||||||
s, _ := v.ToString()
|
|
||||||
fmt.Fprint(ctx.w, NumberColor("%s", s))
|
|
||||||
default:
|
default:
|
||||||
fmt.Fprint(ctx.w, "<unprintable>")
|
if obj, ok := v.(*goja.Object); ok {
|
||||||
|
ctx.printObject(obj, level, inArray)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(ctx.w, "<unprintable %T>", v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
|
// SafeGet attempt to get the value associated to `key`, and
|
||||||
switch obj.Class() {
|
// catches the panic that goja creates if an error occurs in
|
||||||
|
// key.
|
||||||
|
func SafeGet(obj *goja.Object, key string) (ret goja.Value) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ret = goja.Undefined()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ret = obj.Get(key)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) {
|
||||||
|
switch obj.ClassName() {
|
||||||
case "Array", "GoArray":
|
case "Array", "GoArray":
|
||||||
lv, _ := obj.Get("length")
|
lv := obj.Get("length")
|
||||||
len, _ := lv.ToInteger()
|
len := lv.ToInteger()
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
fmt.Fprintf(ctx.w, "[]")
|
fmt.Fprintf(ctx.w, "[]")
|
||||||
return
|
return
|
||||||
@ -121,8 +134,8 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
|
|||||||
}
|
}
|
||||||
fmt.Fprint(ctx.w, "[")
|
fmt.Fprint(ctx.w, "[")
|
||||||
for i := int64(0); i < len; i++ {
|
for i := int64(0); i < len; i++ {
|
||||||
el, err := obj.Get(strconv.FormatInt(i, 10))
|
el := obj.Get(strconv.FormatInt(i, 10))
|
||||||
if err == nil {
|
if el != nil {
|
||||||
ctx.printValue(el, level+1, true)
|
ctx.printValue(el, level+1, true)
|
||||||
}
|
}
|
||||||
if i < len-1 {
|
if i < len-1 {
|
||||||
@ -149,7 +162,7 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
|
|||||||
}
|
}
|
||||||
fmt.Fprintln(ctx.w, "{")
|
fmt.Fprintln(ctx.w, "{")
|
||||||
for i, k := range keys {
|
for i, k := range keys {
|
||||||
v, _ := obj.Get(k)
|
v := SafeGet(obj, k)
|
||||||
fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
|
fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
|
||||||
ctx.printValue(v, level+1, false)
|
ctx.printValue(v, level+1, false)
|
||||||
if i < len(keys)-1 {
|
if i < len(keys)-1 {
|
||||||
@ -163,29 +176,25 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
|
|||||||
fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
|
fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
|
||||||
|
|
||||||
case "Function":
|
case "Function":
|
||||||
// Use toString() to display the argument list if possible.
|
robj := obj.ToString()
|
||||||
if robj, err := obj.Call("toString"); err != nil {
|
|
||||||
fmt.Fprint(ctx.w, FunctionColor("function()"))
|
|
||||||
} else {
|
|
||||||
desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
|
desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
|
||||||
desc = strings.Replace(desc, " (", "(", 1)
|
desc = strings.Replace(desc, " (", "(", 1)
|
||||||
fmt.Fprint(ctx.w, FunctionColor("%s", desc))
|
fmt.Fprint(ctx.w, FunctionColor("%s", desc))
|
||||||
}
|
|
||||||
|
|
||||||
case "RegExp":
|
case "RegExp":
|
||||||
fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
|
fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
|
if level <= maxPrettyPrintLevel {
|
||||||
s, _ := obj.Call("toString")
|
s := obj.ToString().String()
|
||||||
fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String())
|
fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(ctx.w, "<%s>", obj.Class())
|
fmt.Fprintf(ctx.w, "<%s>", obj.ClassName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ppctx) fields(obj *otto.Object) []string {
|
func (ctx ppctx) fields(obj *goja.Object) []string {
|
||||||
var (
|
var (
|
||||||
vals, methods []string
|
vals, methods []string
|
||||||
seen = make(map[string]bool)
|
seen = make(map[string]bool)
|
||||||
@ -195,25 +204,36 @@ func (ctx ppctx) fields(obj *otto.Object) []string {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen[k] = true
|
seen[k] = true
|
||||||
if v, _ := obj.Get(k); v.IsFunction() {
|
|
||||||
|
key := SafeGet(obj, k)
|
||||||
|
if key == nil {
|
||||||
|
// The value corresponding to that key could not be found
|
||||||
|
// (typically because it is backed by an RPC call that is
|
||||||
|
// not supported by this instance. Add it to the list of
|
||||||
|
// values so that it appears as `undefined` to the user.
|
||||||
|
vals = append(vals, k)
|
||||||
|
} else {
|
||||||
|
if _, callable := goja.AssertFunction(key); callable {
|
||||||
methods = append(methods, k)
|
methods = append(methods, k)
|
||||||
} else {
|
} else {
|
||||||
vals = append(vals, k)
|
vals = append(vals, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
iterOwnAndConstructorKeys(ctx.vm, obj, add)
|
iterOwnAndConstructorKeys(ctx.vm, obj, add)
|
||||||
sort.Strings(vals)
|
sort.Strings(vals)
|
||||||
sort.Strings(methods)
|
sort.Strings(methods)
|
||||||
return append(vals, methods...)
|
return append(vals, methods...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
|
func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
iterOwnKeys(vm, obj, func(prop string) {
|
iterOwnKeys(vm, obj, func(prop string) {
|
||||||
seen[prop] = true
|
seen[prop] = true
|
||||||
f(prop)
|
f(prop)
|
||||||
})
|
})
|
||||||
if cp := constructorPrototype(obj); cp != nil {
|
if cp := constructorPrototype(vm, obj); cp != nil {
|
||||||
iterOwnKeys(vm, cp, func(prop string) {
|
iterOwnKeys(vm, cp, func(prop string) {
|
||||||
if !seen[prop] {
|
if !seen[prop] {
|
||||||
f(prop)
|
f(prop)
|
||||||
@ -222,10 +242,17 @@ func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string))
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
|
func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
|
||||||
Object, _ := vm.Object("Object")
|
Object := vm.Get("Object").ToObject(vm)
|
||||||
rv, _ := Object.Call("getOwnPropertyNames", obj.Value())
|
getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames"))
|
||||||
gv, _ := rv.Export()
|
if !isFunc {
|
||||||
|
panic(vm.ToValue("Object.getOwnPropertyNames isn't a function"))
|
||||||
|
}
|
||||||
|
rv, err := getOwnPropertyNames(goja.Null(), obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err)))
|
||||||
|
}
|
||||||
|
gv := rv.Export()
|
||||||
switch gv := gv.(type) {
|
switch gv := gv.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, v := range gv {
|
for _, v := range gv {
|
||||||
@ -240,32 +267,35 @@ func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ppctx) isBigNumber(v *otto.Object) bool {
|
func (ctx ppctx) isBigNumber(v *goja.Object) bool {
|
||||||
// Handle numbers with custom constructor.
|
// Handle numbers with custom constructor.
|
||||||
if v, _ := v.Get("constructor"); v.Object() != nil {
|
if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil {
|
||||||
if strings.HasPrefix(toString(v.Object()), "function BigNumber") {
|
if strings.HasPrefix(toString(obj), "function BigNumber") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle default constructor.
|
// Handle default constructor.
|
||||||
BigNumber, _ := ctx.vm.Object("BigNumber.prototype")
|
BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm)
|
||||||
if BigNumber == nil {
|
if BigNumber == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
bv, _ := BigNumber.Call("isPrototypeOf", v)
|
prototype := BigNumber.Get("prototype").ToObject(ctx.vm)
|
||||||
b, _ := bv.ToBoolean()
|
isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf"))
|
||||||
return b
|
if !callable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bv, _ := isPrototypeOf(prototype, v)
|
||||||
|
return bv.ToBoolean()
|
||||||
}
|
}
|
||||||
|
|
||||||
func toString(obj *otto.Object) string {
|
func toString(obj *goja.Object) string {
|
||||||
s, _ := obj.Call("toString")
|
return obj.ToString().String()
|
||||||
return s.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func constructorPrototype(obj *otto.Object) *otto.Object {
|
func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object {
|
||||||
if v, _ := obj.Get("constructor"); v.Object() != nil {
|
if v := obj.Get("constructor"); v != nil {
|
||||||
if v, _ = v.Object().Get("prototype"); v.Object() != nil {
|
if v := v.ToObject(vm).Get("prototype"); v != nil {
|
||||||
return v.Object()
|
return v.ToObject(vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user