2023-02-09 12:16:36 +01:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2023-03-01 13:04:40 +01:00
|
|
|
"context"
|
2023-02-09 12:16:36 +01:00
|
|
|
"math/rand"
|
|
|
|
"sync"
|
2023-11-28 16:35:49 +01:00
|
|
|
"time"
|
2023-02-09 12:16:36 +01:00
|
|
|
|
|
|
|
"github.com/dop251/goja"
|
|
|
|
"github.com/dop251/goja_nodejs/eventloop"
|
|
|
|
"github.com/pkg/errors"
|
2023-03-01 13:04:40 +01:00
|
|
|
"gitlab.com/wpetit/goweb/logger"
|
2023-02-09 12:16:36 +01:00
|
|
|
)
|
|
|
|
|
2023-03-01 13:04:40 +01:00
|
|
|
var (
|
|
|
|
ErrFuncDoesNotExist = errors.New("function does not exist")
|
2023-10-19 20:05:59 +02:00
|
|
|
ErrUnknownError = errors.New("unknown error")
|
2023-03-01 13:04:40 +01:00
|
|
|
)
|
2023-02-09 12:16:36 +01:00
|
|
|
|
|
|
|
type Server struct {
|
2023-04-24 12:16:30 +02:00
|
|
|
loop *eventloop.EventLoop
|
|
|
|
factories []ServerModuleFactory
|
|
|
|
modules []ServerModule
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-12-05 21:27:43 +01:00
|
|
|
func (s *Server) ExecFuncByName(ctx context.Context, funcName string, args ...any) (any, error) {
|
2023-03-01 13:04:40 +01:00
|
|
|
ctx = logger.With(ctx, logger.F("function", funcName), logger.F("args", args))
|
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
ret, err := s.Exec(ctx, funcName, args...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
return ret, nil
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-12-05 21:27:43 +01:00
|
|
|
func (s *Server) Exec(ctx context.Context, callableOrFuncname any, args ...any) (any, error) {
|
2023-11-28 16:35:49 +01:00
|
|
|
type result struct {
|
2023-12-05 21:27:43 +01:00
|
|
|
value any
|
2023-02-09 12:16:36 +01:00
|
|
|
err error
|
2023-11-28 16:35:49 +01:00
|
|
|
}
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
done := make(chan result)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
// Drain done channel
|
|
|
|
for range done {
|
|
|
|
}
|
|
|
|
}()
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
s.loop.RunOnLoop(func(rt *goja.Runtime) {
|
2023-11-28 16:35:49 +01:00
|
|
|
defer close(done)
|
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
var callable goja.Callable
|
|
|
|
switch typ := callableOrFuncname.(type) {
|
|
|
|
case goja.Callable:
|
|
|
|
callable = typ
|
|
|
|
|
|
|
|
case string:
|
|
|
|
call, ok := goja.AssertFunction(rt.Get(typ))
|
|
|
|
if !ok {
|
2023-11-28 16:35:49 +01:00
|
|
|
done <- result{
|
|
|
|
err: errors.WithStack(ErrFuncDoesNotExist),
|
|
|
|
}
|
2023-04-24 12:16:30 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
callable = call
|
|
|
|
|
|
|
|
default:
|
2023-11-28 16:35:49 +01:00
|
|
|
done <- result{
|
|
|
|
err: errors.Errorf("callableOrFuncname: expected callable or function name, got '%T'", callableOrFuncname),
|
|
|
|
}
|
2023-04-24 12:16:30 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-01 13:04:40 +01:00
|
|
|
defer func() {
|
2023-11-28 16:35:49 +01:00
|
|
|
recovered := recover()
|
|
|
|
if recovered == nil {
|
|
|
|
return
|
|
|
|
}
|
2023-03-01 13:04:40 +01:00
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
recoveredErr, ok := recovered.(error)
|
|
|
|
if !ok {
|
2023-03-01 13:04:40 +01:00
|
|
|
panic(recovered)
|
|
|
|
}
|
2023-11-28 16:35:49 +01:00
|
|
|
|
|
|
|
done <- result{
|
|
|
|
err: recoveredErr,
|
|
|
|
}
|
2023-03-01 13:04:40 +01:00
|
|
|
}()
|
|
|
|
|
2023-02-09 12:16:36 +01:00
|
|
|
jsArgs := make([]goja.Value, 0, len(args))
|
|
|
|
for _, a := range args {
|
2023-04-24 12:16:30 +02:00
|
|
|
jsArgs = append(jsArgs, rt.ToValue(a))
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
logger.Debug(ctx, "executing callable", logger.F("callable", callableOrFuncname))
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
value, err := callable(nil, jsArgs...)
|
2023-02-09 12:16:36 +01:00
|
|
|
if err != nil {
|
2023-11-28 16:35:49 +01:00
|
|
|
done <- result{
|
|
|
|
err: errors.WithStack(err),
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
done <- result{
|
2023-12-05 21:27:43 +01:00
|
|
|
value: value.Export(),
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
2023-11-28 16:35:49 +01:00
|
|
|
|
|
|
|
logger.Debug(ctx, "executed callable", logger.F("callable", callableOrFuncname), logger.F("duration", time.Since(start).String()))
|
2023-02-09 12:16:36 +01:00
|
|
|
})
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
return nil, nil
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
case result := <-done:
|
|
|
|
if result.err != nil {
|
|
|
|
return nil, errors.WithStack(result.err)
|
|
|
|
}
|
|
|
|
|
2023-12-05 21:27:43 +01:00
|
|
|
if promise, ok := isPromise(result.value); ok {
|
2023-12-05 14:14:08 +01:00
|
|
|
return s.waitForPromise(promise), nil
|
2023-11-28 16:35:49 +01:00
|
|
|
}
|
|
|
|
|
2023-12-05 21:27:43 +01:00
|
|
|
return result.value, nil
|
2023-11-28 16:35:49 +01:00
|
|
|
}
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-12-05 14:14:08 +01:00
|
|
|
func (s *Server) waitForPromise(promise *goja.Promise) any {
|
2023-02-09 12:16:36 +01:00
|
|
|
var (
|
|
|
|
wg sync.WaitGroup
|
2023-12-05 14:14:08 +01:00
|
|
|
value any
|
2023-02-09 12:16:36 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-12-05 14:14:08 +01:00
|
|
|
value = promise.Result().Export()
|
2023-02-09 12:16:36 +01:00
|
|
|
|
|
|
|
breakLoop = true
|
|
|
|
})
|
|
|
|
|
|
|
|
loopWait.Wait()
|
|
|
|
|
|
|
|
if breakLoop {
|
|
|
|
wg.Done()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
func (s *Server) Start(ctx context.Context, name string, src string) error {
|
2023-02-09 12:16:36 +01:00
|
|
|
s.loop.Start()
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
done := make(chan error)
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
s.loop.RunOnLoop(func(rt *goja.Runtime) {
|
2023-11-28 16:35:49 +01:00
|
|
|
defer close(done)
|
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
rt.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true))
|
|
|
|
rt.SetRandSource(createRandomSource())
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
if err := s.loadModules(ctx, rt); err != nil {
|
|
|
|
err = errors.WithStack(err)
|
|
|
|
done <- err
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := rt.RunScript(name, src); err != nil {
|
|
|
|
done <- errors.Wrap(err, "could not run js script")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.initModules(ctx, rt); err != nil {
|
2023-04-24 12:16:30 +02:00
|
|
|
err = errors.WithStack(err)
|
2023-11-28 16:35:49 +01:00
|
|
|
done <- err
|
|
|
|
|
|
|
|
return
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
2023-11-28 16:35:49 +01:00
|
|
|
|
|
|
|
done <- nil
|
2023-04-24 12:16:30 +02:00
|
|
|
})
|
2023-11-28 16:35:49 +01:00
|
|
|
|
|
|
|
if err := <-done; err != nil {
|
2023-04-24 12:16:30 +02:00
|
|
|
return errors.WithStack(err)
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) Stop() {
|
|
|
|
s.loop.Stop()
|
|
|
|
}
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
func (s *Server) loadModules(ctx context.Context, rt *goja.Runtime) error {
|
2023-04-24 12:16:30 +02:00
|
|
|
modules := make([]ServerModule, 0, len(s.factories))
|
2023-02-09 12:16:36 +01:00
|
|
|
|
2023-04-24 12:16:30 +02:00
|
|
|
for _, moduleFactory := range s.factories {
|
2023-02-09 12:16:36 +01:00
|
|
|
mod := moduleFactory(s)
|
2023-04-24 12:16:30 +02:00
|
|
|
|
|
|
|
export := rt.NewObject()
|
2023-02-09 12:16:36 +01:00
|
|
|
mod.Export(export)
|
2023-04-24 12:16:30 +02:00
|
|
|
|
|
|
|
rt.Set(mod.Name(), export)
|
2023-02-09 12:16:36 +01:00
|
|
|
|
|
|
|
modules = append(modules, mod)
|
|
|
|
}
|
|
|
|
|
2023-11-28 16:35:49 +01:00
|
|
|
s.modules = modules
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) initModules(ctx context.Context, rt *goja.Runtime) error {
|
|
|
|
for _, mod := range s.modules {
|
2023-04-24 12:16:30 +02:00
|
|
|
initMod, ok := mod.(InitializableModule)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-19 20:05:59 +02:00
|
|
|
logger.Debug(ctx, "initializing module", logger.F("module", initMod.Name()))
|
2023-04-24 12:16:30 +02:00
|
|
|
|
2023-10-19 20:05:59 +02:00
|
|
|
if err := initMod.OnInit(ctx, rt); err != nil {
|
2023-04-24 12:16:30 +02:00
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewServer(factories ...ServerModuleFactory) *Server {
|
|
|
|
server := &Server{
|
2023-04-24 12:16:30 +02:00
|
|
|
factories: factories,
|
2023-02-09 12:16:36 +01:00
|
|
|
loop: eventloop.NewEventLoop(
|
|
|
|
eventloop.EnableConsole(false),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
|
|
|
return server
|
|
|
|
}
|
|
|
|
|
|
|
|
func createRandomSource() goja.RandSource {
|
|
|
|
rnd := rand.New(&cryptoSource{})
|
|
|
|
|
|
|
|
return rnd.Float64
|
|
|
|
}
|