This PR adds Fetch API and fixes #289 by using concurrency safe Otto VM wrapper wherever it's possible. This involves new package geth/jail/vm that is used by jail and by our forked ottoext/{fetch/timers/loop} packages. It also adds more tests that are supposed to be run with --race flag of go test.
This commit is contained in:
parent
39aeb3b09d
commit
6a096607cf
|
@ -255,10 +255,15 @@ type TxQueueManager interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// JailCell represents single jail cell, which is basically a JavaScript VM.
|
// JailCell represents single jail cell, which is basically a JavaScript VM.
|
||||||
|
// It's designed to be a transparent wrapper around otto.VM's methods.
|
||||||
type JailCell interface {
|
type JailCell interface {
|
||||||
|
// Set a value inside VM.
|
||||||
Set(string, interface{}) error
|
Set(string, interface{}) error
|
||||||
|
// Get a value from VM.
|
||||||
Get(string) (otto.Value, error)
|
Get(string) (otto.Value, error)
|
||||||
Run(string) (otto.Value, error)
|
// Run an arbitrary JS code. Input maybe string or otto.Script.
|
||||||
|
Run(interface{}) (otto.Value, error)
|
||||||
|
// Call an arbitrary JS function by name and args.
|
||||||
Call(item string, this interface{}, args ...interface{}) (otto.Value, error)
|
Call(item string, this interface{}, args ...interface{}) (otto.Value, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
# Package jail
|
# Jail
|
||||||
--
|
--
|
||||||
|
|
||||||
import "github.com/status-im/status-go/geth/jail"
|
import "github.com/status-im/status-go/geth/jail"
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/status-im/status-go/geth/jail?status.svg)](https://godoc.org/github.com/status-im/status-go/geth/jail)
|
go:generate godocdown -heading Title -o README.md
|
||||||
|
|
||||||
Package jail implements "jailed" enviroment for executing arbitrary JavaScript
|
Package jail implements "jailed" enviroment for executing arbitrary JavaScript
|
||||||
code using Otto JS interpreter (https://github.com/robertkrimen/otto).
|
code using Otto JS interpreter (https://github.com/robertkrimen/otto).
|
||||||
|
|
||||||
Jail create multiple JailCells, one cell per status client chat. Each cell runs
|
Jail create multiple Cells, one cell per status client chat. Each cell runs own
|
||||||
own Otto virtual machine and lives forever, but that may change in the future.
|
Otto virtual machine and lives forever, but that may change in the future.
|
||||||
|
|
||||||
+----------------------------------------------+
|
+----------------------------------------------+
|
||||||
| Jail |
|
| Jail |
|
||||||
|
@ -24,28 +24,34 @@ own Otto virtual machine and lives forever, but that may change in the future.
|
||||||
++-------++ ++-------++ ++-------++ ++-------++
|
++-------++ ++-------++ ++-------++ ++-------++
|
||||||
|
|
||||||
|
|
||||||
# Get and Set
|
### Cells
|
||||||
|
|
||||||
(*JailCell).Get/Set functions provide transparent and concurrently safe wrappers
|
Each Cell object embeds *VM from 'jail/vm' for concurrency safe wrapper around
|
||||||
for Otto VM Get and Set functions respectively. See Otto documentation for usage
|
*otto.VM functions. This important when dealing with setTimeout and Fetch API
|
||||||
|
functions (see below).
|
||||||
|
|
||||||
|
|
||||||
|
### Get and Set
|
||||||
|
|
||||||
|
(*VM).Get/Set functions provide transparent and concurrently safe wrappers for
|
||||||
|
Otto VM Get and Set functions respectively. See Otto documentation for usage
|
||||||
examples: https://godoc.org/github.com/robertkrimen/otto
|
examples: https://godoc.org/github.com/robertkrimen/otto
|
||||||
|
|
||||||
|
|
||||||
# Call and Run
|
### Call and Run
|
||||||
|
|
||||||
(*JailCell).Call/Run functions allows executing arbitrary JS in the cell.
|
(*VM).Call/Run functions allows executing arbitrary JS in the cell. They're also
|
||||||
They're also wrappers arount Otto VM functions of the same name. Run accepts raw
|
wrappers arount Otto VM functions of the same name. Run accepts raw JS strings
|
||||||
JS strings for execution, Call takes a JS function name (defined in VM) and
|
for execution, Call takes a JS function name (defined in VM) and parameters.
|
||||||
parameters.
|
|
||||||
|
|
||||||
|
|
||||||
# Timeouts and intervals support
|
### Timeouts and intervals support
|
||||||
|
|
||||||
Default Otto VM interpreter doesn't support setTimeout()/setInterval() JS
|
Default Otto VM interpreter doesn't support setTimeout()/setInterval() JS
|
||||||
functions, because they're not part of ECMA-262 spec, but properties of the
|
functions, because they're not part of ECMA-262 spec, but properties of the
|
||||||
window object in browser. We add support for them using
|
window object in browser. We add support for them using
|
||||||
http://github.com/deoxxa/ottoext/timers and
|
http://github.com/status-im/ottoext/timers and
|
||||||
http://github.com/deoxxa/ottoext/loop packages.
|
http://github.com/status-im/ottoext/loop packages.
|
||||||
|
|
||||||
Each cell starts a new loop in a separate goroutine, registers functions for
|
Each cell starts a new loop in a separate goroutine, registers functions for
|
||||||
setTimeout/setInterval calls and associate them with this loop. All JS code
|
setTimeout/setInterval calls and associate them with this loop. All JS code
|
||||||
|
@ -67,6 +73,26 @@ In order to capture response one may use following approach:
|
||||||
cell.Run(`setTimeout(function(){ __captureResponse("OK") }, 2000);`)
|
cell.Run(`setTimeout(function(){ __captureResponse("OK") }, 2000);`)
|
||||||
|
|
||||||
|
|
||||||
# Fetch support
|
### Fetch support
|
||||||
|
|
||||||
### TBD
|
Fetch API is implemented using http://github.com/status-im/ottoext/fetch
|
||||||
|
package. When Cell is created, corresponding handlers are registered within VM
|
||||||
|
and associated event loop.
|
||||||
|
|
||||||
|
Due to asynchronous nature of Fetch API, the following code will return
|
||||||
|
immediately:
|
||||||
|
|
||||||
|
cell.Run(`fetch('http://example.com/').then(function(data) { ... })`)
|
||||||
|
|
||||||
|
### and callback function in a promise will be executed in a event loop in the
|
||||||
|
backgrounds. Thus, it's user responsibility to register a corresponding callback
|
||||||
|
function before:
|
||||||
|
|
||||||
|
cell.Set("__captureSuccess", func(res otto.Value) { ... })
|
||||||
|
|
||||||
|
cell.Run(`fetch('http://example.com').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
// user code
|
||||||
|
__captureSuccess(data)
|
||||||
|
}))
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
//go:generate godocdown -heading Title -o README.md
|
||||||
/*
|
/*
|
||||||
Package jail implements "jailed" enviroment for executing arbitrary
|
Package jail implements "jailed" enviroment for executing arbitrary
|
||||||
JavaScript code using Otto JS interpreter (https://github.com/robertkrimen/otto).
|
JavaScript code using Otto JS interpreter (https://github.com/robertkrimen/otto).
|
||||||
|
|
||||||
Jail create multiple JailCells, one cell per status client chat. Each cell runs own
|
Jail create multiple Cells, one cell per status client chat. Each cell runs own
|
||||||
Otto virtual machine and lives forever, but that may change in the future.
|
Otto virtual machine and lives forever, but that may change in the future.
|
||||||
|
|
||||||
+----------------------------------------------+
|
+----------------------------------------------+
|
||||||
|
@ -17,16 +18,21 @@ Otto virtual machine and lives forever, but that may change in the future.
|
||||||
|| Loop || || Loop || || Loop || || Loop ||
|
|| Loop || || Loop || || Loop || || Loop ||
|
||||||
++-------++ ++-------++ ++-------++ ++-------++
|
++-------++ ++-------++ ++-------++ ++-------++
|
||||||
|
|
||||||
|
Cells
|
||||||
|
|
||||||
|
Each Cell object embeds *VM from 'jail/vm' for concurrency safe wrapper around
|
||||||
|
*otto.VM functions. This important when dealing with setTimeout and Fetch API
|
||||||
|
functions (see below).
|
||||||
|
|
||||||
Get and Set
|
Get and Set
|
||||||
|
|
||||||
(*JailCell).Get/Set functions provide transparent and concurrently safe wrappers for
|
(*VM).Get/Set functions provide transparent and concurrently safe wrappers for
|
||||||
Otto VM Get and Set functions respectively. See Otto documentation for usage examples:
|
Otto VM Get and Set functions respectively. See Otto documentation for usage examples:
|
||||||
https://godoc.org/github.com/robertkrimen/otto
|
https://godoc.org/github.com/robertkrimen/otto
|
||||||
|
|
||||||
Call and Run
|
Call and Run
|
||||||
|
|
||||||
(*JailCell).Call/Run functions allows executing arbitrary JS in the cell. They're also
|
(*VM).Call/Run functions allows executing arbitrary JS in the cell. They're also
|
||||||
wrappers arount Otto VM functions of the same name. Run accepts raw JS strings for execution,
|
wrappers arount Otto VM functions of the same name. Run accepts raw JS strings for execution,
|
||||||
Call takes a JS function name (defined in VM) and parameters.
|
Call takes a JS function name (defined in VM) and parameters.
|
||||||
|
|
||||||
|
@ -34,7 +40,7 @@ Timeouts and intervals support
|
||||||
|
|
||||||
Default Otto VM interpreter doesn't support setTimeout()/setInterval() JS functions,
|
Default Otto VM interpreter doesn't support setTimeout()/setInterval() JS functions,
|
||||||
because they're not part of ECMA-262 spec, but properties of the window object in browser.
|
because they're not part of ECMA-262 spec, but properties of the window object in browser.
|
||||||
We add support for them using http://github.com/deoxxa/ottoext/timers and http://github.com/deoxxa/ottoext/loop
|
We add support for them using http://github.com/status-im/ottoext/timers and http://github.com/status-im/ottoext/loop
|
||||||
packages.
|
packages.
|
||||||
|
|
||||||
Each cell starts a new loop in a separate goroutine, registers functions for setTimeout/setInterval
|
Each cell starts a new loop in a separate goroutine, registers functions for setTimeout/setInterval
|
||||||
|
@ -58,6 +64,24 @@ In order to capture response one may use following approach:
|
||||||
|
|
||||||
Fetch support
|
Fetch support
|
||||||
|
|
||||||
TBD
|
Fetch API is implemented using http://github.com/status-im/ottoext/fetch package. When
|
||||||
|
Cell is created, corresponding handlers are registered within VM and associated event loop.
|
||||||
|
|
||||||
|
Due to asynchronous nature of Fetch API, the following code will return immediately:
|
||||||
|
|
||||||
|
cell.Run(`fetch('http://example.com/').then(function(data) { ... })`)
|
||||||
|
|
||||||
|
and callback function in a promise will be executed in a event loop in the backgrounds. Thus,
|
||||||
|
it's user responsibility to register a corresponding callback function before:
|
||||||
|
|
||||||
|
cell.Set("__captureSuccess", func(res otto.Value) { ... })
|
||||||
|
|
||||||
|
cell.Run(`fetch('http://example.com').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
// user code
|
||||||
|
__captureSuccess(data)
|
||||||
|
}))
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package jail
|
package jail
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
ottoext
|
ottoext
|
||||||
=======
|
=======
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/status-im/ottoext?status.svg)](https://godoc.org/github.com/status-im/ottoext)
|
Originally based on [github.com/deoxxa/ottoext](https://github.com/deoxxa/ottoext)
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/status-im/status-go/geth/jail/ottoext?status.svg)](https://godoc.org/github.com/status-im/status-go/geth/jail/ottoext)
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
|
@ -10,8 +10,9 @@ import (
|
||||||
"github.com/GeertJohan/go.rice"
|
"github.com/GeertJohan/go.rice"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
"github.com/status-im/ottoext/loop"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
"github.com/status-im/ottoext/promise"
|
"github.com/status-im/status-go/geth/jail/internal/promise"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustValue(v otto.Value, err error) otto.Value {
|
func mustValue(v otto.Value, err error) otto.Value {
|
||||||
|
@ -36,7 +37,7 @@ type fetchTask struct {
|
||||||
func (t *fetchTask) SetID(id int64) { t.id = id }
|
func (t *fetchTask) SetID(id int64) { t.id = id }
|
||||||
func (t *fetchTask) GetID() int64 { return t.id }
|
func (t *fetchTask) GetID() int64 { return t.id }
|
||||||
|
|
||||||
func (t *fetchTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
func (t *fetchTask) Execute(vm *vm.VM, l *loop.Loop) error {
|
||||||
var arguments []interface{}
|
var arguments []interface{}
|
||||||
|
|
||||||
if t.err != nil {
|
if t.err != nil {
|
||||||
|
@ -48,6 +49,12 @@ func (t *fetchTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
||||||
arguments = append(arguments, e)
|
arguments = append(arguments, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're locking on VM here because underlying otto's VM
|
||||||
|
// is not concurrently safe, and this function indirectly
|
||||||
|
// access vm's functions in cb.Call/h.Set.
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
t.jsRes.Set("status", t.status)
|
t.jsRes.Set("status", t.status)
|
||||||
t.jsRes.Set("statusText", t.statusText)
|
t.jsRes.Set("statusText", t.statusText)
|
||||||
h := mustValue(t.jsRes.Get("headers")).Object()
|
h := mustValue(t.jsRes.Get("headers")).Object()
|
||||||
|
@ -70,11 +77,11 @@ func (t *fetchTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
||||||
func (t *fetchTask) Cancel() {
|
func (t *fetchTask) Cancel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Define(vm *otto.Otto, l *loop.Loop) error {
|
func Define(vm *vm.VM, l *loop.Loop) error {
|
||||||
return DefineWithHandler(vm, l, nil)
|
return DefineWithHandler(vm, l, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefineWithHandler(vm *otto.Otto, l *loop.Loop, h http.Handler) error {
|
func DefineWithHandler(vm *vm.VM, l *loop.Loop, h http.Handler) error {
|
||||||
if err := promise.Define(vm, l); err != nil {
|
if err := promise.Define(vm, l); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package fetch_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/fetch"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetch() {
|
||||||
|
ch := make(chan struct{})
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("hello"))
|
||||||
|
ch <- struct{}{}
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`fetch('` + s.srv.URL + `').then(function(r) {
|
||||||
|
return r.text();
|
||||||
|
}).then(function(d) {
|
||||||
|
if (d.indexOf('hellox') === -1) {
|
||||||
|
throw new Error('what');
|
||||||
|
}
|
||||||
|
});`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetchCallback() {
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
err = s.vm.Set("__capture", func(str string) {
|
||||||
|
s.Contains(str, "hello")
|
||||||
|
ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`fetch('` + s.srv.URL + `').then(function(r) {
|
||||||
|
return r.text();
|
||||||
|
}).then(__capture)`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetchHeaders() {
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("header-one", "1")
|
||||||
|
w.Header().Add("header-two", "2a")
|
||||||
|
w.Header().Add("header-two", "2b")
|
||||||
|
|
||||||
|
w.Write([]byte("hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
err = s.vm.Set("__capture", func(str string) {
|
||||||
|
s.Equal(str, `{"header-one":["1"],"header-two":["2a","2b"]}`)
|
||||||
|
ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`fetch('` + s.srv.URL + `').then(function(r) {
|
||||||
|
return __capture(JSON.stringify({
|
||||||
|
'header-one': r.headers.getAll('header-one'),
|
||||||
|
'header-two': r.headers.getAll('header-two'),
|
||||||
|
}));
|
||||||
|
})`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetchJSON() {
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// these spaces are here so we can disambiguate between this and the
|
||||||
|
// re-encoded data the javascript below spits out
|
||||||
|
w.Write([]byte("[ 1 , 2 , 3 ]"))
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
err = s.vm.Set("__capture", func(str string) {
|
||||||
|
s.Equal(str, `[1,2,3]`)
|
||||||
|
ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`fetch('` + s.srv.URL + `').then(function(r) { return r.json(); }).then(function(d) {
|
||||||
|
return setTimeout(__capture, 4, JSON.stringify(d));
|
||||||
|
})`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetchWithHandler() {
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// these spaces are here so we can disambiguate between this and the
|
||||||
|
// re-encoded data the javascript below spits out
|
||||||
|
w.Write([]byte("[ 1 , 2 , 3 ]"))
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.DefineWithHandler(s.vm, s.loop, s.mux)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
err = s.vm.Set("__capture", func(str string) {
|
||||||
|
s.Equal(str, `[1,2,3]`)
|
||||||
|
ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`fetch('/').then(function(r) { return r.json(); }).then(function(d) {
|
||||||
|
return setTimeout(__capture, 4, JSON.stringify(d));
|
||||||
|
})`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TestFetchWithHandlerParallel() {
|
||||||
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
err := fetch.DefineWithHandler(s.vm, s.loop, s.mux)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
err = s.vm.Set("__capture", func(c otto.FunctionCall) otto.Value {
|
||||||
|
ch <- struct{}{}
|
||||||
|
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`Promise.all([1,2,3,4,5].map(function(i) { return fetch('/' + i).then(__capture); }))`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
timerCh := time.After(1 * time.Second)
|
||||||
|
var count int
|
||||||
|
loop:
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
count++
|
||||||
|
case <-timerCh:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Equal(5, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
mux *http.ServeMux
|
||||||
|
srv *httptest.Server
|
||||||
|
|
||||||
|
loop *loop.Loop
|
||||||
|
vm *vm.VM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) SetupTest() {
|
||||||
|
s.mux = http.NewServeMux()
|
||||||
|
s.srv = httptest.NewServer(s.mux)
|
||||||
|
|
||||||
|
o := otto.New()
|
||||||
|
s.vm = vm.New(o)
|
||||||
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
|
go s.loop.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FetchSuite) TearDownSuite() {
|
||||||
|
s.srv.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(FetchSuite))
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatTask(t Task) string {
|
func formatTask(t Task) string {
|
||||||
|
@ -29,7 +29,7 @@ func formatTask(t Task) string {
|
||||||
type Task interface {
|
type Task interface {
|
||||||
SetID(id int64)
|
SetID(id int64)
|
||||||
GetID() int64
|
GetID() int64
|
||||||
Execute(vm *otto.Otto, l *Loop) error
|
Execute(vm *vm.VM, l *Loop) error
|
||||||
Cancel()
|
Cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ type Task interface {
|
||||||
// to finalise on the VM. The channel holding the tasks pending finalising can
|
// to finalise on the VM. The channel holding the tasks pending finalising can
|
||||||
// be buffered or unbuffered.
|
// be buffered or unbuffered.
|
||||||
type Loop struct {
|
type Loop struct {
|
||||||
vm *otto.Otto
|
vm *vm.VM
|
||||||
id int64
|
id int64
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
tasks map[int64]Task
|
tasks map[int64]Task
|
||||||
|
@ -48,13 +48,13 @@ type Loop struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Loop with an unbuffered ready queue on a specific VM.
|
// New creates a new Loop with an unbuffered ready queue on a specific VM.
|
||||||
func New(vm *otto.Otto) *Loop {
|
func New(vm *vm.VM) *Loop {
|
||||||
return NewWithBacklog(vm, 0)
|
return NewWithBacklog(vm, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithBacklog creates a new Loop on a specific VM, giving it a buffered
|
// NewWithBacklog creates a new Loop on a specific VM, giving it a buffered
|
||||||
// queue, the capacity of which being specified by the backlog argument.
|
// queue, the capacity of which being specified by the backlog argument.
|
||||||
func NewWithBacklog(vm *otto.Otto, backlog int) *Loop {
|
func NewWithBacklog(vm *vm.VM, backlog int) *Loop {
|
||||||
return &Loop{
|
return &Loop{
|
||||||
vm: vm,
|
vm: vm,
|
||||||
tasks: make(map[int64]Task),
|
tasks: make(map[int64]Task),
|
||||||
|
@ -62,10 +62,8 @@ func NewWithBacklog(vm *otto.Otto, backlog int) *Loop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VM gets the JavaScript interpreter associated with the loop. This will be
|
// VM gets the JavaScript interpreter associated with the loop.
|
||||||
// some kind of Otto object, but it's wrapped in an interface so the
|
func (l *Loop) VM() *vm.VM {
|
||||||
// `ottoext` library can work with forks/extensions of otto.
|
|
||||||
func (l *Loop) VM() *otto.Otto {
|
|
||||||
return l.vm
|
return l.vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,15 +105,6 @@ func (l *Loop) Ready(t Task) {
|
||||||
l.ready <- t
|
l.ready <- t
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvalAndRun is a combination of Eval and Run. Creatively named.
|
|
||||||
func (l *Loop) EvalAndRun(s interface{}) error {
|
|
||||||
if err := l.Eval(s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eval executes some code in the VM associated with the loop and returns an
|
// Eval executes some code in the VM associated with the loop and returns an
|
||||||
// error if that execution fails.
|
// error if that execution fails.
|
||||||
func (l *Loop) Eval(s interface{}) error {
|
func (l *Loop) Eval(s interface{}) error {
|
|
@ -3,8 +3,9 @@ package looptask
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/status-im/ottoext/loop"
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IdleTask is designed to sit in a loop and keep it active, without doing any
|
// IdleTask is designed to sit in a loop and keep it active, without doing any
|
||||||
|
@ -29,7 +30,7 @@ func (i IdleTask) Cancel() {}
|
||||||
|
|
||||||
// Execute always returns an error for an IdleTask, as it should never
|
// Execute always returns an error for an IdleTask, as it should never
|
||||||
// actually be run.
|
// actually be run.
|
||||||
func (i IdleTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
func (i IdleTask) Execute(vm *vm.VM, l *loop.Loop) error {
|
||||||
return errors.New("Idle task should never execute")
|
return errors.New("Idle task should never execute")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ func (e EvalTask) Cancel() {}
|
||||||
// Execute runs the EvalTask's otto.Script in the vm provided, pushing the
|
// Execute runs the EvalTask's otto.Script in the vm provided, pushing the
|
||||||
// resultant return value and error (or nil) into the associated channels.
|
// resultant return value and error (or nil) into the associated channels.
|
||||||
// If the execution results in an error, it will return that error.
|
// If the execution results in an error, it will return that error.
|
||||||
func (e EvalTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
func (e EvalTask) Execute(vm *vm.VM, l *loop.Loop) error {
|
||||||
v, err := vm.Run(e.Script)
|
v, err := vm.Run(e.Script)
|
||||||
e.Value <- v
|
e.Value <- v
|
||||||
e.Error <- err
|
e.Error <- err
|
|
@ -1,13 +1,12 @@
|
||||||
package promise
|
package promise
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/timers"
|
||||||
"github.com/status-im/ottoext/loop"
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
"github.com/status-im/ottoext/timers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Define(vm *otto.Otto, l *loop.Loop) error {
|
func Define(vm *vm.VM, l *loop.Loop) error {
|
||||||
if v, err := vm.Get("Promise"); err != nil {
|
if v, err := vm.Get("Promise"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !v.IsUndefined() {
|
} else if !v.IsUndefined() {
|
|
@ -0,0 +1,101 @@
|
||||||
|
package promise_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/promise"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *PromiseSuite) TestResolve() {
|
||||||
|
err := s.vm.Set("__resolve", func(str string) {
|
||||||
|
defer func() { s.ch <- struct{}{} }()
|
||||||
|
|
||||||
|
s.Equal("good", str)
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
setTimeout(function() {
|
||||||
|
resolve('good');
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
p.then(function(d) {
|
||||||
|
__resolve(d);
|
||||||
|
});
|
||||||
|
|
||||||
|
p.catch(function(err) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PromiseSuite) TestReject() {
|
||||||
|
err := s.vm.Set("__reject", func(str string) {
|
||||||
|
defer func() { s.ch <- struct{}{} }()
|
||||||
|
|
||||||
|
s.Equal("bad", str)
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
setTimeout(function() {
|
||||||
|
reject('bad');
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
p.catch(function(err) {
|
||||||
|
__reject(err);
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PromiseSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
loop *loop.Loop
|
||||||
|
vm *vm.VM
|
||||||
|
|
||||||
|
ch chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PromiseSuite) SetupTest() {
|
||||||
|
o := otto.New()
|
||||||
|
s.vm = vm.New(o)
|
||||||
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
|
go s.loop.Run()
|
||||||
|
|
||||||
|
err := promise.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.ch = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromiseSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(PromiseSuite))
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
"github.com/status-im/ottoext/loop"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var minDelay = map[bool]int64{
|
var minDelay = map[bool]int64{
|
||||||
|
@ -13,7 +14,7 @@ var minDelay = map[bool]int64{
|
||||||
false: 4,
|
false: 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Define(vm *otto.Otto, l *loop.Loop) error {
|
func Define(vm *vm.VM, l *loop.Loop) error {
|
||||||
if v, err := vm.Get("setTimeout"); err != nil {
|
if v, err := vm.Get("setTimeout"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !v.IsUndefined() {
|
} else if !v.IsUndefined() {
|
||||||
|
@ -97,7 +98,7 @@ type timerTask struct {
|
||||||
func (t *timerTask) SetID(id int64) { t.id = id }
|
func (t *timerTask) SetID(id int64) { t.id = id }
|
||||||
func (t *timerTask) GetID() int64 { return t.id }
|
func (t *timerTask) GetID() int64 { return t.id }
|
||||||
|
|
||||||
func (t *timerTask) Execute(vm *otto.Otto, l *loop.Loop) error {
|
func (t *timerTask) Execute(vm *vm.VM, l *loop.Loop) error {
|
||||||
var arguments []interface{}
|
var arguments []interface{}
|
||||||
|
|
||||||
if len(t.call.ArgumentList) > 2 {
|
if len(t.call.ArgumentList) > 2 {
|
|
@ -0,0 +1,119 @@
|
||||||
|
package timers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/timers"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *TimersSuite) TestSetTimeout() {
|
||||||
|
err := s.vm.Set("__capture", func() {
|
||||||
|
s.ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`setTimeout(function(n) {
|
||||||
|
if (Date.now() - n < 50) {
|
||||||
|
throw new Error('timeout was called too soon');
|
||||||
|
}
|
||||||
|
__capture();
|
||||||
|
}, 50, Date.now());`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.ch:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimersSuite) TestClearTimeout() {
|
||||||
|
err := s.vm.Set("__shouldNeverRun", func() {
|
||||||
|
s.Fail("should never run")
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`clearTimeout(setTimeout(function() {
|
||||||
|
__shouldNeverRun();
|
||||||
|
}, 50));`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
<-time.After(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimersSuite) TestSetInterval() {
|
||||||
|
err := s.vm.Set("__done", func() {
|
||||||
|
s.ch <- struct{}{}
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`
|
||||||
|
var c = 0;
|
||||||
|
var iv = setInterval(function() {
|
||||||
|
if (c === 1) {
|
||||||
|
clearInterval(iv);
|
||||||
|
__done();
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
}, 50);
|
||||||
|
`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.ch:
|
||||||
|
value, err := s.vm.Get("c")
|
||||||
|
s.NoError(err)
|
||||||
|
n, err := value.ToInteger()
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(2, int(n))
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
s.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimersSuite) TestClearIntervalImmediately() {
|
||||||
|
err := s.vm.Set("__shouldNeverRun", func() {
|
||||||
|
s.Fail("should never run")
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
err = s.loop.Eval(`clearInterval(setInterval(function() {
|
||||||
|
__shouldNeverRun();
|
||||||
|
}, 50));`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
<-time.After(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimersSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
loop *loop.Loop
|
||||||
|
vm *vm.VM
|
||||||
|
|
||||||
|
ch chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimersSuite) SetupTest() {
|
||||||
|
o := otto.New()
|
||||||
|
s.vm = vm.New(o)
|
||||||
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
|
go s.loop.Run()
|
||||||
|
|
||||||
|
err := timers.Define(s.vm, s.loop)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.ch = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimersSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(TimersSuite))
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VM implements concurrency safe wrapper to
|
||||||
|
// otto's VM object.
|
||||||
|
type VM struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
vm *otto.Otto
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates new instance of VM.
|
||||||
|
func New(vm *otto.Otto) *VM {
|
||||||
|
return &VM{
|
||||||
|
vm: vm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value to be keyed by the provided keyname.
|
||||||
|
func (vm *VM) Set(key string, val interface{}) error {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.Set(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the giving key's otto.Value from the underline otto vm.
|
||||||
|
func (vm *VM) Get(key string) (otto.Value, error) {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call attempts to call the internal call function for the giving response associated with the
|
||||||
|
// proper values.
|
||||||
|
func (vm *VM) Call(item string, this interface{}, args ...interface{}) (otto.Value, error) {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.Call(item, this, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run evaluates JS source, which may be string or otto.Script variable.
|
||||||
|
func (vm *VM) Run(src interface{}) (otto.Value, error) {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.Run(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile parses given source and returns otto.Script.
|
||||||
|
func (vm *VM) Compile(filename string, src interface{}) (*otto.Script, error) {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.Compile(filename, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileWithSourceMap parses given source with source map and returns otto.Script.
|
||||||
|
func (vm *VM) CompileWithSourceMap(filename string, src, sm interface{}) (*otto.Script, error) {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.CompileWithSourceMap(filename, src, sm)
|
||||||
|
}
|
|
@ -1,74 +1,50 @@
|
||||||
package jail
|
package jail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/ottoext/loop"
|
"github.com/status-im/status-go/geth/jail/internal/fetch"
|
||||||
"github.com/status-im/ottoext/timers"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/timers"
|
||||||
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cell represents a single jail cell, which is basically a JavaScript VM.
|
// Cell represents a single jail cell, which is basically a JavaScript VM.
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
id string
|
id string
|
||||||
vm *otto.Otto
|
*vm.VM
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCell encapsulates what we need to create a new jailCell from the
|
// newCell encapsulates what we need to create a new jailCell from the
|
||||||
// provided vm and eventloop instance.
|
// provided vm and eventloop instance.
|
||||||
func newCell(id string, vm *otto.Otto) (*Cell, error) {
|
func newCell(id string, ottoVM *otto.Otto) (*Cell, error) {
|
||||||
// create new event loop for the new cell.
|
cellVM := vm.New(ottoVM)
|
||||||
// this loop is handling 'setTimeout/setInterval'
|
|
||||||
// calls and is running endlessly in a separate goroutine
|
|
||||||
lo := loop.New(vm)
|
|
||||||
|
|
||||||
// register handlers for setTimeout/setInterval
|
lo := loop.New(cellVM)
|
||||||
// functions
|
|
||||||
if err := timers.Define(vm, lo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally, start loop in a goroutine
|
registerVMHandlers(cellVM, lo)
|
||||||
|
|
||||||
|
// start loop in a goroutine
|
||||||
// Cell is currently immortal, so the loop
|
// Cell is currently immortal, so the loop
|
||||||
go lo.Run()
|
go lo.Run()
|
||||||
|
|
||||||
return &Cell{
|
return &Cell{
|
||||||
id: id,
|
id: id,
|
||||||
vm: vm,
|
VM: cellVM,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the value to be keyed by the provided keyname.
|
// registerHandlers register variuous functions and handlers
|
||||||
func (cell *Cell) Set(key string, val interface{}) error {
|
// to the Otto VM, such as Fetch API callbacks or promises.
|
||||||
cell.Lock()
|
func registerVMHandlers(v *vm.VM, lo *loop.Loop) error {
|
||||||
defer cell.Unlock()
|
// setTimeout/setInterval functions
|
||||||
|
if err := timers.Define(v, lo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return cell.vm.Set(key, val)
|
// FetchAPI functions
|
||||||
}
|
if err := fetch.Define(v, lo); err != nil {
|
||||||
|
return err
|
||||||
// Get returns the giving key's otto.Value from the underline otto vm.
|
}
|
||||||
func (cell *Cell) Get(key string) (otto.Value, error) {
|
|
||||||
cell.Lock()
|
return nil
|
||||||
defer cell.Unlock()
|
|
||||||
|
|
||||||
return cell.vm.Get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call attempts to call the internal call function for the giving response associated with the
|
|
||||||
// proper values.
|
|
||||||
func (cell *Cell) Call(item string, this interface{}, args ...interface{}) (otto.Value, error) {
|
|
||||||
cell.Lock()
|
|
||||||
defer cell.Unlock()
|
|
||||||
|
|
||||||
return cell.vm.Call(item, this, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run evaluates the giving js string on the associated vm llop.
|
|
||||||
func (cell *Cell) Run(val string) (otto.Value, error) {
|
|
||||||
cell.Lock()
|
|
||||||
defer cell.Unlock()
|
|
||||||
|
|
||||||
return cell.vm.Run(val)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package jail_test
|
package jail_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/params"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *JailTestSuite) TestJailTimeoutFailure() {
|
func (s *JailTestSuite) TestJailTimeoutFailure() {
|
||||||
|
@ -70,9 +71,6 @@ func (s *JailTestSuite) TestJailTimeout() {
|
||||||
func (s *JailTestSuite) TestJailLoopInCall() {
|
func (s *JailTestSuite) TestJailLoopInCall() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
|
|
||||||
s.StartTestNode(params.RopstenNetworkID)
|
|
||||||
defer s.StopTestNode()
|
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
// load Status JS and add test command to it
|
||||||
s.jail.BaseJS(baseStatusJSCode)
|
s.jail.BaseJS(baseStatusJSCode)
|
||||||
s.jail.Parse(testChatID, ``)
|
s.jail.Parse(testChatID, ``)
|
||||||
|
@ -109,3 +107,188 @@ func (s *JailTestSuite) TestJailLoopInCall() {
|
||||||
require.Fail("Failed to received event response")
|
require.Fail("Failed to received event response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestJailLoopRace tests multiple setTimeout callbacks,
|
||||||
|
// supposed to be run with '-race' flag.
|
||||||
|
func (s *JailTestSuite) TestJailLoopRace() {
|
||||||
|
require := s.Require()
|
||||||
|
|
||||||
|
cell, err := s.jail.NewCell(testChatID)
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotNil(cell)
|
||||||
|
|
||||||
|
items := make(chan struct{})
|
||||||
|
|
||||||
|
err = cell.Set("__captureResponse", func() otto.Value {
|
||||||
|
go func() { items <- struct{}{} }()
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
_, err = cell.Run(`
|
||||||
|
function callRunner(){
|
||||||
|
return setTimeout(function(){
|
||||||
|
__captureResponse();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
_, err = cell.Call("callRunner", nil)
|
||||||
|
require.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
select {
|
||||||
|
case <-items:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
require.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailFetchPromise() {
|
||||||
|
body := `{"key": "value"}`
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(body))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
require := s.Require()
|
||||||
|
|
||||||
|
cell, err := s.jail.NewCell(testChatID)
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotNil(cell)
|
||||||
|
|
||||||
|
dataCh := make(chan otto.Value, 1)
|
||||||
|
errCh := make(chan otto.Value, 1)
|
||||||
|
|
||||||
|
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// run JS code for fetching valid URL
|
||||||
|
_, err = cell.Run(`fetch('` + server.URL + `').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
__captureSuccess(data)
|
||||||
|
}).catch(function (e) {
|
||||||
|
__captureError(e)
|
||||||
|
})`)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case data := <-dataCh:
|
||||||
|
require.True(data.IsString())
|
||||||
|
require.Equal(body, data.String())
|
||||||
|
case err := <-errCh:
|
||||||
|
require.Fail("request failed", err)
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
require.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailFetchCatch() {
|
||||||
|
require := s.Require()
|
||||||
|
|
||||||
|
cell, err := s.jail.NewCell(testChatID)
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotNil(cell)
|
||||||
|
|
||||||
|
dataCh := make(chan otto.Value, 1)
|
||||||
|
errCh := make(chan otto.Value, 1)
|
||||||
|
|
||||||
|
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// run JS code for fetching invalid URL
|
||||||
|
_, err = cell.Run(`fetch('http://👽/nonexistent').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
__captureSuccess(data)
|
||||||
|
}).catch(function (e) {
|
||||||
|
__captureError(e)
|
||||||
|
})`)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case data := <-dataCh:
|
||||||
|
require.Fail("request should have failed, but returned", data)
|
||||||
|
case e := <-errCh:
|
||||||
|
require.True(e.IsObject())
|
||||||
|
name, err := e.Object().Get("name")
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("Error", name.String())
|
||||||
|
_, err = e.Object().Get("message")
|
||||||
|
require.NoError(err)
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
require.Fail("test timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestJailFetchRace tests multiple fetch callbacks,
|
||||||
|
// supposed to be run with '-race' flag.
|
||||||
|
func (s *JailTestSuite) TestJailFetchRace() {
|
||||||
|
body := `{"key": "value"}`
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(body))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
require := s.Require()
|
||||||
|
|
||||||
|
cell, err := s.jail.NewCell(testChatID)
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotNil(cell)
|
||||||
|
|
||||||
|
dataCh := make(chan otto.Value, 1)
|
||||||
|
errCh := make(chan otto.Value, 1)
|
||||||
|
|
||||||
|
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// run JS code for fetching valid URL
|
||||||
|
_, err = cell.Run(`fetch('` + server.URL + `').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
__captureSuccess(data)
|
||||||
|
}).catch(function (e) {
|
||||||
|
__captureError(e)
|
||||||
|
})`)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// run JS code for fetching invalid URL
|
||||||
|
_, err = cell.Run(`fetch('http://👽/nonexistent').then(function(r) {
|
||||||
|
return r.text()
|
||||||
|
}).then(function(data) {
|
||||||
|
__captureSuccess(data)
|
||||||
|
}).catch(function (e) {
|
||||||
|
__captureError(e)
|
||||||
|
})`)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
select {
|
||||||
|
case data := <-dataCh:
|
||||||
|
require.True(data.IsString())
|
||||||
|
require.Equal(body, data.String())
|
||||||
|
case e := <-errCh:
|
||||||
|
require.True(e.IsObject())
|
||||||
|
name, err := e.Object().Get("name")
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("Error", name.String())
|
||||||
|
_, err = e.Object().Get("message")
|
||||||
|
require.NoError(err)
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
require.Fail("test timed out")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
/ottoext
|
|
|
@ -1,12 +0,0 @@
|
||||||
var x = fetch('http://www.example.com/').then(function(r) {
|
|
||||||
r.text().then(function(d) {
|
|
||||||
console.log(r.statusText);
|
|
||||||
|
|
||||||
for (var k in r.headers._headers) {
|
|
||||||
console.log(k + ':', r.headers.get(k));
|
|
||||||
}
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
console.log(d);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,90 +0,0 @@
|
||||||
// command otto runs JavaScript from a file, opens a repl, or does both.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/status-im/ottoext/loop"
|
|
||||||
"github.com/status-im/ottoext/loop/looptask"
|
|
||||||
erepl "github.com/status-im/ottoext/repl"
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
"github.com/robertkrimen/otto/repl"
|
|
||||||
|
|
||||||
"github.com/status-im/ottoext/fetch"
|
|
||||||
"github.com/status-im/ottoext/process"
|
|
||||||
"github.com/status-im/ottoext/promise"
|
|
||||||
"github.com/status-im/ottoext/timers"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
openRepl = flag.Bool("repl", false, "Always open a REPL, even if a file is provided.")
|
|
||||||
debugger = flag.Bool("debugger", false, "Attach REPL-based debugger.")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
vm := otto.New()
|
|
||||||
|
|
||||||
if *debugger {
|
|
||||||
vm.SetDebuggerHandler(repl.DebuggerHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := loop.New(vm)
|
|
||||||
|
|
||||||
if err := timers.Define(vm, l); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := promise.Define(vm, l); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := fetch.Define(vm, l); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := process.Define(vm, flag.Args()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
blockingTask := looptask.NewEvalTask("")
|
|
||||||
|
|
||||||
if len(flag.Args()) == 0 || *openRepl {
|
|
||||||
l.Add(blockingTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(flag.Args()) > 0 {
|
|
||||||
d, err := ioutil.ReadFile(flag.Arg(0))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is a very cheap way of "supporting" shebang lines
|
|
||||||
if d[0] == '#' {
|
|
||||||
d = []byte("// " + string(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := vm.Compile(flag.Arg(0), string(d))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.Eval(s); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(flag.Args()) == 0 || *openRepl {
|
|
||||||
go func() {
|
|
||||||
if err := erepl.Run(l); err != nil && err != io.EOF {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Ready(blockingTask)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.Run(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,236 +0,0 @@
|
||||||
package repl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func seenWith(seen map[otto.Value]bool, v otto.Value) map[otto.Value]bool {
|
|
||||||
r := make(map[otto.Value]bool)
|
|
||||||
for k, v := range seen {
|
|
||||||
r[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
r[v] = true
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func format(v otto.Value, width, indent, limit int) (string, error) {
|
|
||||||
return formatIndent(v, width, indent, limit, 0, 0, make(map[otto.Value]bool))
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatIndent(v otto.Value, width, indent, limit, level, additional int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if limit == 0 {
|
|
||||||
return "...", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case v.IsBoolean(), v.IsNull(), v.IsNumber(), v.IsUndefined():
|
|
||||||
return v.String(), nil
|
|
||||||
case v.IsString():
|
|
||||||
return fmt.Sprintf("%q", v.String()), nil
|
|
||||||
case v.IsFunction():
|
|
||||||
n, err := v.Object().Get("name")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if n.IsUndefined() {
|
|
||||||
return "function", nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("function %s", n.String()), nil
|
|
||||||
case v.IsObject():
|
|
||||||
if d, err := formatOneLine(v, limit, seen); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if level*indent+additional+len(d) <= width {
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Class() {
|
|
||||||
case "Array":
|
|
||||||
return formatArray(v, width, indent, limit, level, seen)
|
|
||||||
default:
|
|
||||||
return formatObject(v, width, indent, limit, level, seen)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("couldn't format type %s", v.Class())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatArray(v otto.Value, width, indent, limit, level int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if seen[v] {
|
|
||||||
return strings.Repeat(" ", level*indent) + "[circular]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
o := v.Object()
|
|
||||||
|
|
||||||
lv, err := o.Get("length")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
li, err := lv.Export()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
l, ok := li.(uint32)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("length property must be a number; was %T", li)
|
|
||||||
}
|
|
||||||
|
|
||||||
bits := []string{"["}
|
|
||||||
|
|
||||||
for i := 0; i < int(l); i++ {
|
|
||||||
e, err := o.Get(fmt.Sprintf("%d", i))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := formatIndent(e, width, indent, limit-1, level+1, 0, seenWith(seen, v))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, strings.Repeat(" ", (level+1)*indent)+d+",")
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, strings.Repeat(" ", level*indent)+"]")
|
|
||||||
|
|
||||||
return strings.Join(bits, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatObject(v otto.Value, width, indent, limit, level int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if seen[v] {
|
|
||||||
return strings.Repeat(" ", level*indent) + "[circular]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
o := v.Object()
|
|
||||||
|
|
||||||
bits := []string{"{"}
|
|
||||||
|
|
||||||
keys := o.Keys()
|
|
||||||
|
|
||||||
for i, k := range keys {
|
|
||||||
e, err := o.Get(k)
|
|
||||||
|
|
||||||
d, err := formatIndent(e, width, indent, limit-1, level+1, len(k)+2, seenWith(seen, v))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, strings.Repeat(" ", (level+1)*indent)+k+": "+d+",")
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, strings.Repeat(" ", level*indent)+"}")
|
|
||||||
|
|
||||||
return strings.Join(bits, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatOneLine(v otto.Value, limit int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if limit == 0 {
|
|
||||||
return "...", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case v.IsBoolean(), v.IsNull(), v.IsNumber(), v.IsUndefined():
|
|
||||||
return v.String(), nil
|
|
||||||
case v.IsString():
|
|
||||||
return fmt.Sprintf("%q", v.String()), nil
|
|
||||||
case v.IsFunction():
|
|
||||||
n, err := v.Object().Get("name")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if n.IsUndefined() {
|
|
||||||
return "function", nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("function %s", n.String()), nil
|
|
||||||
case v.IsObject():
|
|
||||||
switch v.Class() {
|
|
||||||
case "Array":
|
|
||||||
return formatArrayOneLine(v, limit, seen)
|
|
||||||
default:
|
|
||||||
return formatObjectOneLine(v, limit, seen)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("couldn't format type %s", v.Class())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatArrayOneLine(v otto.Value, limit int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if limit == 0 {
|
|
||||||
return "...", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen[v] {
|
|
||||||
return "[circular]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
o := v.Object()
|
|
||||||
|
|
||||||
lv, err := o.Get("length")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
li, err := lv.Export()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
l, ok := li.(uint32)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("length property must be a number; was %T", li)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bits []string
|
|
||||||
|
|
||||||
for i := 0; i < int(l); i++ {
|
|
||||||
e, err := o.Get(fmt.Sprintf("%d", i))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := formatOneLine(e, limit-1, seenWith(seen, v))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "[" + strings.Join(bits, ", ") + "]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatObjectOneLine(v otto.Value, limit int, seen map[otto.Value]bool) (string, error) {
|
|
||||||
if limit == 0 {
|
|
||||||
return "...", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen[v] {
|
|
||||||
return "[circular]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
o := v.Object()
|
|
||||||
|
|
||||||
bits := []string{}
|
|
||||||
|
|
||||||
keys := o.Keys()
|
|
||||||
|
|
||||||
for _, k := range keys {
|
|
||||||
e, err := o.Get(k)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := formatOneLine(e, limit-1, seenWith(seen, v))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bits = append(bits, k+": "+d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{" + strings.Join(bits, ", ") + "}", nil
|
|
||||||
}
|
|
|
@ -1,151 +0,0 @@
|
||||||
// Package repl implements an event loop aware REPL (read-eval-print loop)
|
|
||||||
// for otto.
|
|
||||||
package repl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
"github.com/robertkrimen/otto/parser"
|
|
||||||
"github.com/status-im/ottoext/loop"
|
|
||||||
"github.com/status-im/ottoext/loop/looptask"
|
|
||||||
"gopkg.in/readline.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run creates a REPL with the default prompt and no prelude.
|
|
||||||
func Run(l *loop.Loop) error {
|
|
||||||
return RunWithPromptAndPrelude(l, "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithPrompt runs a REPL with the given prompt and no prelude.
|
|
||||||
func RunWithPrompt(l *loop.Loop, prompt string) error {
|
|
||||||
return RunWithPromptAndPrelude(l, prompt, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithPrelude runs a REPL with the default prompt and the given prelude.
|
|
||||||
func RunWithPrelude(l *loop.Loop, prelude string) error {
|
|
||||||
return RunWithPromptAndPrelude(l, "", prelude)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithPromptAndPrelude runs a REPL with the given prompt and prelude.
|
|
||||||
func RunWithPromptAndPrelude(l *loop.Loop, prompt, prelude string) error {
|
|
||||||
if prompt == "" {
|
|
||||||
prompt = ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt = strings.Trim(prompt, " ")
|
|
||||||
prompt += " "
|
|
||||||
|
|
||||||
rl, err := readline.New(prompt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.VM().Set("console", map[string]interface{}{
|
|
||||||
"log": func(c otto.FunctionCall) otto.Value {
|
|
||||||
s := make([]string, len(c.ArgumentList))
|
|
||||||
for i := 0; i < len(c.ArgumentList); i++ {
|
|
||||||
s[i] = c.Argument(i).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.Stdout().Write([]byte(strings.Join(s, " ") + "\n"))
|
|
||||||
rl.Refresh()
|
|
||||||
|
|
||||||
return otto.UndefinedValue()
|
|
||||||
},
|
|
||||||
"warn": func(c otto.FunctionCall) otto.Value {
|
|
||||||
s := make([]string, len(c.ArgumentList))
|
|
||||||
for i := 0; i < len(c.ArgumentList); i++ {
|
|
||||||
s[i] = c.Argument(i).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.Stderr().Write([]byte(strings.Join(s, " ") + "\n"))
|
|
||||||
rl.Refresh()
|
|
||||||
|
|
||||||
return otto.UndefinedValue()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if prelude != "" {
|
|
||||||
if _, err := io.Copy(rl.Stderr(), strings.NewReader(prelude+"\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
var d []string
|
|
||||||
|
|
||||||
for {
|
|
||||||
ll, err := rl.Readline()
|
|
||||||
if err != nil {
|
|
||||||
if err == readline.ErrInterrupt {
|
|
||||||
if d != nil {
|
|
||||||
d = nil
|
|
||||||
|
|
||||||
rl.SetPrompt(prompt)
|
|
||||||
rl.Refresh()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(d) == 0 && ll == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d = append(d, ll)
|
|
||||||
s := strings.Join(d, "\n")
|
|
||||||
|
|
||||||
if _, err := parser.ParseFile(nil, "repl", s, 0); err != nil {
|
|
||||||
rl.SetPrompt(strings.Repeat(" ", len(prompt)))
|
|
||||||
} else {
|
|
||||||
rl.SetPrompt(prompt)
|
|
||||||
|
|
||||||
d = nil
|
|
||||||
|
|
||||||
t := looptask.NewEvalTask(s)
|
|
||||||
// don't report errors to the loop - this lets us handle them and
|
|
||||||
// resume normal operation
|
|
||||||
t.SoftError = true
|
|
||||||
l.Add(t)
|
|
||||||
l.Ready(t)
|
|
||||||
|
|
||||||
v, err := <-t.Value, <-t.Error
|
|
||||||
if err != nil {
|
|
||||||
if oerr, ok := err.(*otto.Error); ok {
|
|
||||||
io.Copy(rl.Stdout(), strings.NewReader(oerr.String()))
|
|
||||||
} else {
|
|
||||||
io.Copy(rl.Stdout(), strings.NewReader(err.Error()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f, err := format(v, 80, 2, 5)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.Stdout().Write([]byte("\r" + f + "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
return rl.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func inspect(v otto.Value, width, indent int) string {
|
|
||||||
switch {
|
|
||||||
case v.IsBoolean(), v.IsNull(), v.IsNumber(), v.IsString(), v.IsUndefined(), v.IsNaN():
|
|
||||||
return fmt.Sprintf("%s%q", strings.Repeat(" ", indent), v.String())
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue