183 lines
3.2 KiB
Go
183 lines
3.2 KiB
Go
package app
|
|
|
|
import (
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"github.com/dop251/goja"
|
|
"github.com/dop251/goja_nodejs/eventloop"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var ErrFuncDoesNotExist = errors.New("function does not exist")
|
|
|
|
type Server struct {
|
|
runtime *goja.Runtime
|
|
loop *eventloop.EventLoop
|
|
modules []ServerModule
|
|
}
|
|
|
|
func (s *Server) Load(name string, src string) error {
|
|
_, err := s.runtime.RunScript(name, src)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not run js script")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) ExecFuncByName(funcName string, args ...interface{}) (goja.Value, error) {
|
|
callable, ok := goja.AssertFunction(s.runtime.Get(funcName))
|
|
if !ok {
|
|
return nil, errors.WithStack(ErrFuncDoesNotExist)
|
|
}
|
|
|
|
return s.Exec(callable, args...)
|
|
}
|
|
|
|
func (s *Server) Exec(callable goja.Callable, args ...interface{}) (goja.Value, error) {
|
|
var (
|
|
wg sync.WaitGroup
|
|
value goja.Value
|
|
err error
|
|
)
|
|
|
|
wg.Add(1)
|
|
|
|
s.loop.RunOnLoop(func(vm *goja.Runtime) {
|
|
jsArgs := make([]goja.Value, 0, len(args))
|
|
for _, a := range args {
|
|
jsArgs = append(jsArgs, vm.ToValue(a))
|
|
}
|
|
|
|
value, err = callable(nil, jsArgs...)
|
|
if err != nil {
|
|
err = errors.WithStack(err)
|
|
}
|
|
|
|
wg.Done()
|
|
})
|
|
|
|
wg.Wait()
|
|
|
|
return value, err
|
|
}
|
|
|
|
func (s *Server) IsPromise(v goja.Value) (*goja.Promise, bool) {
|
|
promise, ok := v.Export().(*goja.Promise)
|
|
return promise, ok
|
|
}
|
|
|
|
func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value {
|
|
var (
|
|
wg sync.WaitGroup
|
|
value goja.Value
|
|
)
|
|
|
|
wg.Add(1)
|
|
|
|
// Wait for promise completion
|
|
go func() {
|
|
for {
|
|
var loopWait sync.WaitGroup
|
|
loopWait.Add(1)
|
|
|
|
breakLoop := false
|
|
|
|
s.loop.RunOnLoop(func(vm *goja.Runtime) {
|
|
defer loopWait.Done()
|
|
|
|
if promise.State() == goja.PromiseStatePending {
|
|
return
|
|
}
|
|
|
|
value = promise.Result()
|
|
|
|
breakLoop = true
|
|
})
|
|
|
|
loopWait.Wait()
|
|
|
|
if breakLoop {
|
|
wg.Done()
|
|
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
return value
|
|
}
|
|
|
|
func (s *Server) NewPromise() *PromiseProxy {
|
|
promise, resolve, reject := s.runtime.NewPromise()
|
|
|
|
return NewPromiseProxy(promise, resolve, reject)
|
|
}
|
|
|
|
func (s *Server) ToValue(v interface{}) goja.Value {
|
|
return s.runtime.ToValue(v)
|
|
}
|
|
|
|
func (s *Server) Start() error {
|
|
s.loop.Start()
|
|
|
|
for _, mod := range s.modules {
|
|
initMod, ok := mod.(InitializableModule)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if err := initMod.OnInit(); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Stop() {
|
|
s.loop.Stop()
|
|
}
|
|
|
|
func (s *Server) initModules(factories ...ServerModuleFactory) {
|
|
runtime := goja.New()
|
|
|
|
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true))
|
|
runtime.SetRandSource(createRandomSource())
|
|
|
|
modules := make([]ServerModule, 0, len(factories))
|
|
|
|
for _, moduleFactory := range factories {
|
|
mod := moduleFactory(s)
|
|
export := runtime.NewObject()
|
|
mod.Export(export)
|
|
runtime.Set(mod.Name(), export)
|
|
|
|
modules = append(modules, mod)
|
|
}
|
|
|
|
s.runtime = runtime
|
|
s.modules = modules
|
|
}
|
|
|
|
func NewServer(factories ...ServerModuleFactory) *Server {
|
|
server := &Server{
|
|
loop: eventloop.NewEventLoop(
|
|
eventloop.EnableConsole(false),
|
|
),
|
|
}
|
|
|
|
server.initModules(factories...)
|
|
|
|
return server
|
|
}
|
|
|
|
func createRandomSource() goja.RandSource {
|
|
rnd := rand.New(&cryptoSource{})
|
|
|
|
return rnd.Float64
|
|
}
|