edge/pkg/app/backend.go

183 lines
3.2 KiB
Go
Raw Normal View History

2023-02-09 12:16:36 +01:00
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
}