stash/pkg/javascript/vm.go

94 lines
2.0 KiB
Go

// Package javascript provides the javascript runtime for the application.
package javascript
import (
"fmt"
"os"
"reflect"
"github.com/dop251/goja"
"github.com/stashapp/stash/pkg/logger"
)
// VM is a wrapper around goja.Runtime.
type VM struct {
*goja.Runtime
}
// optionalFieldNameMapper wraps a goja.FieldNameMapper and returns the field name if the wrapped mapper returns an empty string.
type optionalFieldNameMapper struct {
mapper goja.FieldNameMapper
}
func (tfm optionalFieldNameMapper) FieldName(t reflect.Type, f reflect.StructField) string {
if ret := tfm.mapper.FieldName(t, f); ret != "" {
return ret
}
return f.Name
}
func (tfm optionalFieldNameMapper) MethodName(t reflect.Type, m reflect.Method) string {
return tfm.mapper.MethodName(t, m)
}
func NewVM() *VM {
r := goja.New()
// enable console for backwards compatibility
c := console{
Log{
Logger: logger.Logger,
},
}
// there should not be any reason for this to fail
_ = c.AddToVM("console", &VM{Runtime: r})
r.SetFieldNameMapper(optionalFieldNameMapper{goja.TagFieldNameMapper("json", true)})
return &VM{Runtime: r}
}
type APIAdder interface {
AddToVM(globalName string, vm *VM) error
}
type ObjectValueDef struct {
Name string
Value interface{}
}
type setter interface {
Set(name string, value interface{}) error
}
func Compile(path string) (*goja.Program, error) {
js, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
return goja.Compile(path, string(js), true)
}
func CompileScript(name, script string) (*goja.Program, error) {
return goja.Compile(name, string(script), true)
}
func SetAll(s setter, defs ...ObjectValueDef) error {
for _, def := range defs {
if err := s.Set(def.Name, def.Value); err != nil {
return fmt.Errorf("failed to set %s: %w", def.Name, err)
}
}
return nil
}
func (v *VM) Throw(err error) {
e, newErr := v.New(v.Get("Error"), v.ToValue(err))
if newErr != nil {
panic(newErr)
}
panic(e)
}