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 Backend struct { runtime *goja.Runtime loop *eventloop.EventLoop modules []BackendModule } func (b *Backend) Load(name string, src string) error { _, err := b.runtime.RunScript(name, src) if err != nil { return errors.Wrap(err, "could not run js script") } return nil } func (b *Backend) ExecFuncByName(funcName string, args ...interface{}) (goja.Value, error) { callable, ok := goja.AssertFunction(b.runtime.Get(funcName)) if !ok { return nil, errors.WithStack(ErrFuncDoesNotExist) } return b.Exec(callable, args...) } func (b *Backend) Exec(callable goja.Callable, args ...interface{}) (goja.Value, error) { var ( wg sync.WaitGroup value goja.Value err error ) wg.Add(1) b.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 (b *Backend) IsPromise(v goja.Value) (*goja.Promise, bool) { promise, ok := v.Export().(*goja.Promise) return promise, ok } func (b *Backend) 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 b.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 (b *Backend) NewPromise() *PromiseProxy { promise, resolve, reject := b.runtime.NewPromise() return NewPromiseProxy(promise, resolve, reject) } func (b *Backend) ToValue(v interface{}) goja.Value { return b.runtime.ToValue(v) } func (b *Backend) Start() error { b.loop.Start() for _, mod := range b.modules { initMod, ok := mod.(InitializableModule) if !ok { continue } if err := initMod.OnInit(); err != nil { return errors.WithStack(err) } } return nil } func (b *Backend) Stop() { b.loop.Stop() } func (b *Backend) initModules(factories ...BackendModuleFactory) { runtime := goja.New() runtime.SetFieldNameMapper(goja.UncapFieldNameMapper()) runtime.SetRandSource(createRandomSource()) modules := make([]BackendModule, 0, len(factories)) for _, moduleFactory := range factories { mod := moduleFactory(b) export := runtime.NewObject() mod.Export(export) runtime.Set(mod.Name(), export) modules = append(modules, mod) } b.runtime = runtime b.modules = modules } func NewBackend(factories ...BackendModuleFactory) *Backend { backend := &Backend{ loop: eventloop.NewEventLoop( eventloop.EnableConsole(false), ), } backend.initModules(factories...) return backend } func createRandomSource() goja.RandSource { rnd := rand.New(&cryptoSource{}) return rnd.Float64 }