script_surge_generic.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package script
  2. import (
  3. "context"
  4. "runtime"
  5. "time"
  6. "github.com/sagernet/sing-box/adapter"
  7. C "github.com/sagernet/sing-box/constant"
  8. "github.com/sagernet/sing-box/experimental/locale"
  9. "github.com/sagernet/sing-box/option"
  10. "github.com/sagernet/sing-box/script/jsc"
  11. "github.com/sagernet/sing-box/script/modules/console"
  12. "github.com/sagernet/sing-box/script/modules/eventloop"
  13. "github.com/sagernet/sing-box/script/modules/require"
  14. "github.com/sagernet/sing-box/script/modules/sghttp"
  15. "github.com/sagernet/sing-box/script/modules/sgnotification"
  16. "github.com/sagernet/sing-box/script/modules/sgstore"
  17. "github.com/sagernet/sing-box/script/modules/sgutils"
  18. "github.com/sagernet/sing/common"
  19. E "github.com/sagernet/sing/common/exceptions"
  20. F "github.com/sagernet/sing/common/format"
  21. "github.com/sagernet/sing/common/logger"
  22. "github.com/sagernet/sing/common/ntp"
  23. "github.com/dop251/goja"
  24. "github.com/dop251/goja/parser"
  25. )
  26. const defaultScriptTimeout = 10 * time.Second
  27. var _ adapter.GenericScript = (*GenericScript)(nil)
  28. type GenericScript struct {
  29. logger logger.ContextLogger
  30. tag string
  31. timeout time.Duration
  32. arguments []any
  33. source Source
  34. }
  35. func NewSurgeGenericScript(ctx context.Context, logger logger.ContextLogger, options option.Script) (*GenericScript, error) {
  36. source, err := NewSource(ctx, logger, options)
  37. if err != nil {
  38. return nil, err
  39. }
  40. return &GenericScript{
  41. logger: logger,
  42. tag: options.Tag,
  43. timeout: time.Duration(options.Timeout),
  44. arguments: options.Arguments,
  45. source: source,
  46. }, nil
  47. }
  48. func (s *GenericScript) Type() string {
  49. return C.ScriptTypeSurgeGeneric
  50. }
  51. func (s *GenericScript) Tag() string {
  52. return s.tag
  53. }
  54. func (s *GenericScript) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {
  55. return s.source.StartContext(ctx, startContext)
  56. }
  57. func (s *GenericScript) PostStart() error {
  58. return s.source.PostStart()
  59. }
  60. func (s *GenericScript) Close() error {
  61. return s.source.Close()
  62. }
  63. func (s *GenericScript) Run(ctx context.Context) error {
  64. program := s.source.Program()
  65. if program == nil {
  66. return E.New("invalid script")
  67. }
  68. ctx, cancel := context.WithCancelCause(ctx)
  69. defer cancel(nil)
  70. vm := NewRuntime(ctx, s.logger, cancel)
  71. err := SetSurgeModules(vm, ctx, s.logger, cancel, s.Tag(), s.Type(), s.arguments)
  72. if err != nil {
  73. return err
  74. }
  75. return ExecuteSurgeGeneral(vm, program, ctx, s.timeout)
  76. }
  77. func NewRuntime(ctx context.Context, logger logger.ContextLogger, cancel context.CancelCauseFunc) *goja.Runtime {
  78. vm := goja.New()
  79. if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
  80. vm.SetTimeSource(timeFunc)
  81. }
  82. vm.SetParserOptions(parser.WithDisableSourceMaps)
  83. registry := require.NewRegistry(require.WithLoader(func(path string) ([]byte, error) {
  84. return nil, E.New("unsupported usage")
  85. }))
  86. registry.Enable(vm)
  87. registry.RegisterNodeModule(console.ModuleName, console.Require(ctx, logger))
  88. console.Enable(vm)
  89. eventloop.Enable(vm, cancel)
  90. return vm
  91. }
  92. func SetSurgeModules(vm *goja.Runtime, ctx context.Context, logger logger.Logger, errorHandler func(error), tag string, scriptType string, arguments []any) error {
  93. script := vm.NewObject()
  94. script.Set("name", F.ToString("script:", tag))
  95. script.Set("startTime", jsc.TimeToValue(vm, time.Now()))
  96. script.Set("type", scriptType)
  97. vm.Set("$script", script)
  98. environment := vm.NewObject()
  99. var system string
  100. switch runtime.GOOS {
  101. case "ios":
  102. system = "iOS"
  103. case "darwin":
  104. system = "macOS"
  105. case "tvos":
  106. system = "tvOS"
  107. case "linux":
  108. system = "Linux"
  109. case "android":
  110. system = "Android"
  111. case "windows":
  112. system = "Windows"
  113. default:
  114. system = runtime.GOOS
  115. }
  116. environment.Set("system", system)
  117. environment.Set("surge-build", "N/A")
  118. environment.Set("surge-version", "sing-box "+C.Version)
  119. environment.Set("language", locale.Current().Locale)
  120. environment.Set("device-model", "N/A")
  121. vm.Set("$environment", environment)
  122. sgstore.Enable(vm, ctx)
  123. sgutils.Enable(vm)
  124. sghttp.Enable(vm, ctx, errorHandler)
  125. sgnotification.Enable(vm, ctx, logger)
  126. vm.Set("$argument", arguments)
  127. return nil
  128. }
  129. func ExecuteSurgeGeneral(vm *goja.Runtime, program *goja.Program, ctx context.Context, timeout time.Duration) error {
  130. if timeout == 0 {
  131. timeout = defaultScriptTimeout
  132. }
  133. ctx, cancel := context.WithTimeout(ctx, timeout)
  134. defer cancel()
  135. vm.ClearInterrupt()
  136. done := make(chan struct{})
  137. doneFunc := common.OnceFunc(func() {
  138. close(done)
  139. })
  140. vm.Set("done", func(call goja.FunctionCall) goja.Value {
  141. doneFunc()
  142. return goja.Undefined()
  143. })
  144. var err error
  145. go func() {
  146. _, err = vm.RunProgram(program)
  147. if err != nil {
  148. doneFunc()
  149. }
  150. }()
  151. select {
  152. case <-ctx.Done():
  153. vm.Interrupt(ctx.Err())
  154. return ctx.Err()
  155. case <-done:
  156. if err != nil {
  157. vm.Interrupt(err)
  158. } else {
  159. vm.Interrupt("script done")
  160. }
  161. }
  162. return err
  163. }