123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- 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
- }
|