@ -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 . ArgumentLi st ) == 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 ! callable {
return nil , fmt . Errorf ( "jeth.newAccount is not callable" )
}
ret , err := newAccount ( goja . Null ( ) , call . VM . ToValue ( password ) )
if err != nil {
if err != nil {
throwJSException ( err . Error ( ) )
return nil , err
}
}
return ret
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 )
}
openWallet , callable := goja . AssertFunction ( getJeth ( call . VM ) . Get ( "openWallet" ) )
if ! callable {
return nil , fmt . Errorf ( "jeth.openWallet is not callable" )
}
}
return call . Otto . Call ( "jeth.openWallet" , nil , wallet , passwd )
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 )
}
openWallet , callable := goja . AssertFunction ( getJeth ( call . VM ) . Get ( "openWallet" ) )
if ! callable {
return nil , fmt . Errorf ( "jeth.openWallet is not callable" )
}
}
return call . Otto . Call ( "jeth.openWallet" , nil , wallet , passwd )
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 )
}
}
}
if ! passwd . IsString ( ) {
passwd = call . VM . ToValue ( input )
throwJSException ( "third argument must be the password to unlock the account" )
} else if passwd . ExportType ( ) . Kind ( ) != reflect . String {
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" )
}
}
}
// go through the console, this will allow web3 to call the appropriate
sleep = call . Argument ( 1 ) . ToInteger ( )
// 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 ( )
// 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 ) {
var number hexutil . Uint64
err := b . client . Call ( & number , "eth_blockNumber" )
if err != nil {
if err != nil {
throwJSException ( err . Error ( ) )
return nil , err
}
}
return block
if number != lastNumber {
lastNumber = number
blocks --
}
}
// Poll the current block number until either it ot a timeout is reached
if blocks <= 0 {
targetBlockNr := blockNumber ( ) + blocks
break
deadline := time . Now ( ) . Add ( time . Duration ( sleep ) * time . Second )
for time . Now ( ) . Before ( deadline ) {
if blockNumber ( ) >= targetBlockNr {
return otto . TrueValue ( )
}
}
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 )
func getObject ( vm * goja . Runtime , name string ) * goja . Object {
v := vm . Get ( name )
if v == nil {
return nil
}
}
panic ( val )
return v . ToObject ( vm )
}
}