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.UncapFieldNameMapper()) 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 }