mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-03 20:07:36 +03:00
183 lines
4.7 KiB
Go
183 lines
4.7 KiB
Go
package script
|
|
|
|
import (
|
|
"context"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
C "github.com/sagernet/sing-box/constant"
|
|
"github.com/sagernet/sing-box/experimental/locale"
|
|
"github.com/sagernet/sing-box/option"
|
|
"github.com/sagernet/sing-box/script/jsc"
|
|
"github.com/sagernet/sing-box/script/modules/console"
|
|
"github.com/sagernet/sing-box/script/modules/eventloop"
|
|
"github.com/sagernet/sing-box/script/modules/require"
|
|
"github.com/sagernet/sing-box/script/modules/sghttp"
|
|
"github.com/sagernet/sing-box/script/modules/sgnotification"
|
|
"github.com/sagernet/sing-box/script/modules/sgstore"
|
|
"github.com/sagernet/sing-box/script/modules/sgutils"
|
|
"github.com/sagernet/sing/common"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
F "github.com/sagernet/sing/common/format"
|
|
"github.com/sagernet/sing/common/logger"
|
|
"github.com/sagernet/sing/common/ntp"
|
|
|
|
"github.com/dop251/goja"
|
|
"github.com/dop251/goja/parser"
|
|
)
|
|
|
|
const defaultScriptTimeout = 10 * time.Second
|
|
|
|
var _ adapter.GenericScript = (*GenericScript)(nil)
|
|
|
|
type GenericScript struct {
|
|
logger logger.ContextLogger
|
|
tag string
|
|
timeout time.Duration
|
|
arguments []any
|
|
source Source
|
|
}
|
|
|
|
func NewSurgeGenericScript(ctx context.Context, logger logger.ContextLogger, options option.Script) (*GenericScript, error) {
|
|
source, err := NewSource(ctx, logger, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &GenericScript{
|
|
logger: logger,
|
|
tag: options.Tag,
|
|
timeout: time.Duration(options.Timeout),
|
|
arguments: options.Arguments,
|
|
source: source,
|
|
}, nil
|
|
}
|
|
|
|
func (s *GenericScript) Type() string {
|
|
return C.ScriptTypeSurgeGeneric
|
|
}
|
|
|
|
func (s *GenericScript) Tag() string {
|
|
return s.tag
|
|
}
|
|
|
|
func (s *GenericScript) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {
|
|
return s.source.StartContext(ctx, startContext)
|
|
}
|
|
|
|
func (s *GenericScript) PostStart() error {
|
|
return s.source.PostStart()
|
|
}
|
|
|
|
func (s *GenericScript) Close() error {
|
|
return s.source.Close()
|
|
}
|
|
|
|
func (s *GenericScript) Run(ctx context.Context) error {
|
|
program := s.source.Program()
|
|
if program == nil {
|
|
return E.New("invalid script")
|
|
}
|
|
ctx, cancel := context.WithCancelCause(ctx)
|
|
defer cancel(nil)
|
|
vm := NewRuntime(ctx, s.logger, cancel)
|
|
err := SetSurgeModules(vm, ctx, s.logger, cancel, s.Tag(), s.Type(), s.arguments)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ExecuteSurgeGeneral(vm, program, ctx, s.timeout)
|
|
}
|
|
|
|
func NewRuntime(ctx context.Context, logger logger.ContextLogger, cancel context.CancelCauseFunc) *goja.Runtime {
|
|
vm := goja.New()
|
|
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
|
|
vm.SetTimeSource(timeFunc)
|
|
}
|
|
vm.SetParserOptions(parser.WithDisableSourceMaps)
|
|
registry := require.NewRegistry(require.WithLoader(func(path string) ([]byte, error) {
|
|
return nil, E.New("unsupported usage")
|
|
}))
|
|
registry.Enable(vm)
|
|
registry.RegisterNodeModule(console.ModuleName, console.Require(ctx, logger))
|
|
console.Enable(vm)
|
|
eventloop.Enable(vm, cancel)
|
|
return vm
|
|
}
|
|
|
|
func SetSurgeModules(vm *goja.Runtime, ctx context.Context, logger logger.Logger, errorHandler func(error), tag string, scriptType string, arguments []any) error {
|
|
script := vm.NewObject()
|
|
script.Set("name", F.ToString("script:", tag))
|
|
script.Set("startTime", jsc.TimeToValue(vm, time.Now()))
|
|
script.Set("type", scriptType)
|
|
vm.Set("$script", script)
|
|
|
|
environment := vm.NewObject()
|
|
var system string
|
|
switch runtime.GOOS {
|
|
case "ios":
|
|
system = "iOS"
|
|
case "darwin":
|
|
system = "macOS"
|
|
case "tvos":
|
|
system = "tvOS"
|
|
case "linux":
|
|
system = "Linux"
|
|
case "android":
|
|
system = "Android"
|
|
case "windows":
|
|
system = "Windows"
|
|
default:
|
|
system = runtime.GOOS
|
|
}
|
|
environment.Set("system", system)
|
|
environment.Set("surge-build", "N/A")
|
|
environment.Set("surge-version", "sing-box "+C.Version)
|
|
environment.Set("language", locale.Current().Locale)
|
|
environment.Set("device-model", "N/A")
|
|
vm.Set("$environment", environment)
|
|
|
|
sgstore.Enable(vm, ctx)
|
|
sgutils.Enable(vm)
|
|
sghttp.Enable(vm, ctx, errorHandler)
|
|
sgnotification.Enable(vm, ctx, logger)
|
|
|
|
vm.Set("$argument", arguments)
|
|
return nil
|
|
}
|
|
|
|
func ExecuteSurgeGeneral(vm *goja.Runtime, program *goja.Program, ctx context.Context, timeout time.Duration) error {
|
|
if timeout == 0 {
|
|
timeout = defaultScriptTimeout
|
|
}
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
vm.ClearInterrupt()
|
|
done := make(chan struct{})
|
|
doneFunc := common.OnceFunc(func() {
|
|
close(done)
|
|
})
|
|
vm.Set("done", func(call goja.FunctionCall) goja.Value {
|
|
doneFunc()
|
|
return goja.Undefined()
|
|
})
|
|
var err error
|
|
go func() {
|
|
_, err = vm.RunProgram(program)
|
|
if err != nil {
|
|
doneFunc()
|
|
}
|
|
}()
|
|
select {
|
|
case <-ctx.Done():
|
|
vm.Interrupt(ctx.Err())
|
|
return ctx.Err()
|
|
case <-done:
|
|
if err != nil {
|
|
vm.Interrupt(err)
|
|
} else {
|
|
vm.Interrupt("script done")
|
|
}
|
|
}
|
|
return err
|
|
}
|