2022-01-02 00:22:04 +00:00
|
|
|
package job_compilers
|
|
|
|
|
2022-03-07 14:26:46 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
2022-01-10 12:36:39 +00:00
|
|
|
|
2022-01-02 00:22:04 +00:00
|
|
|
import (
|
|
|
|
"embed"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/dop251/goja"
|
2022-01-10 16:43:30 +00:00
|
|
|
"github.com/dop251/goja_nodejs/require"
|
2022-01-02 00:22:04 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
)
|
|
|
|
|
2022-01-03 15:54:38 +00:00
|
|
|
//go:embed scripts
|
2022-01-02 00:22:04 +00:00
|
|
|
var scriptsFS embed.FS
|
|
|
|
|
2022-01-10 16:43:30 +00:00
|
|
|
func (s *Service) loadScripts() error {
|
2022-01-02 00:22:04 +00:00
|
|
|
scripts, err := scriptsFS.ReadDir("scripts")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to find scripts: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, script := range scripts {
|
2022-01-03 16:58:58 +00:00
|
|
|
if !strings.HasSuffix(script.Name(), ".js") {
|
|
|
|
continue
|
|
|
|
}
|
2022-01-02 00:22:04 +00:00
|
|
|
filename := path.Join("scripts", script.Name())
|
|
|
|
|
2022-01-10 16:43:30 +00:00
|
|
|
script_bytes, err := s.loadScriptBytes(filename)
|
2022-01-02 00:22:04 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Str("filename", filename).Msg("failed to read script")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
program, err := goja.Compile(filename, string(script_bytes), true)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Str("filename", filename).Msg("failed to compile script")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-01-10 14:24:40 +00:00
|
|
|
jobTypeName := filenameToJobType(script.Name())
|
2022-01-10 16:43:30 +00:00
|
|
|
s.compilers[jobTypeName] = Compiler{
|
2022-02-21 18:31:37 +00:00
|
|
|
jobType: jobTypeName,
|
2022-01-10 14:24:40 +00:00
|
|
|
program: program,
|
|
|
|
filename: script.Name(),
|
|
|
|
}
|
2022-01-02 00:22:04 +00:00
|
|
|
|
2022-01-10 14:24:40 +00:00
|
|
|
log.Debug().Str("script", script.Name()).Str("jobType", jobTypeName).Msg("loaded script")
|
2022-01-02 00:22:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-10 16:43:30 +00:00
|
|
|
func (s *Service) loadScriptBytes(path string) ([]byte, error) {
|
2022-01-03 15:54:38 +00:00
|
|
|
file, err := scriptsFS.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to open embedded script: %w", err)
|
|
|
|
}
|
|
|
|
return io.ReadAll(file)
|
|
|
|
}
|
|
|
|
|
2022-01-02 00:22:04 +00:00
|
|
|
func filenameToJobType(filename string) string {
|
|
|
|
extension := path.Ext(filename)
|
|
|
|
stem := filename[:len(filename)-len(extension)]
|
|
|
|
return strings.ReplaceAll(stem, "_", "-")
|
|
|
|
}
|
2022-01-10 16:43:30 +00:00
|
|
|
|
|
|
|
func (s *Service) newGojaVM() *goja.Runtime {
|
|
|
|
vm := goja.New()
|
|
|
|
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
|
|
|
|
|
|
|
mustSet := func(name string, value interface{}) {
|
|
|
|
err := vm.Set(name, value)
|
|
|
|
if err != nil {
|
|
|
|
log.Panic().Err(err).Msgf("unable to register '%s' in Goja VM", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-13 15:32:08 +00:00
|
|
|
// Set some global functions.
|
2022-01-13 15:35:31 +00:00
|
|
|
mustSet("print", jsPrint)
|
|
|
|
mustSet("alert", jsAlert)
|
|
|
|
mustSet("frameChunker", jsFrameChunker)
|
|
|
|
mustSet("formatTimestampLocal", jsFormatTimestampLocal)
|
2022-01-10 16:43:30 +00:00
|
|
|
|
|
|
|
// Pre-import some useful modules.
|
|
|
|
s.registry.Enable(vm)
|
|
|
|
mustSet("author", require.Require(vm, "author"))
|
|
|
|
mustSet("path", require.Require(vm, "path"))
|
|
|
|
mustSet("process", require.Require(vm, "process"))
|
|
|
|
|
|
|
|
return vm
|
|
|
|
}
|
|
|
|
|
|
|
|
// compilerForJobType returns a Goja *Runtime that has the job compiler script for
|
|
|
|
// the given job type loaded up.
|
|
|
|
func (s *Service) compilerForJobType(jobTypeName string) (*VM, error) {
|
|
|
|
program, ok := s.compilers[jobTypeName]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrJobTypeUnknown
|
|
|
|
}
|
|
|
|
|
|
|
|
vm := s.newGojaVM()
|
|
|
|
if _, err := vm.RunProgram(program.program); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &VM{
|
|
|
|
runtime: vm,
|
|
|
|
compiler: program,
|
|
|
|
}, nil
|
|
|
|
}
|