package gojs import ( "apigo.cloud/git/apigo/plugin" "apigo.cloud/git/apigo/qjs" "errors" "fmt" "github.com/ssgo/log" "github.com/ssgo/u" "regexp" "strings" ) var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`) type JSRuntime struct { freeJsValues []quickjs.Value rt quickjs.Runtime JsCtx *quickjs.Context GoCtx *plugin.Context logger *log.Logger plugins map[string]*plugin.Plugin } func (rt *JSRuntime) Close() { for _, v := range rt.freeJsValues { v.Free() } rt.freeJsValues = make([]quickjs.Value, 0) rt.JsCtx.Close() rt.rt.Close() } func (rt *JSRuntime) initCode(code string) { tryPlugins := map[string]bool{} for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) { tryPlugins[m[1]] = true } for _, plg := range plugin.List() { if tryPlugins[plg.Id] && rt.plugins[plg.Id] == nil { rt.plugins[plg.Id] = &plg rt.JsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false)) if plg.JsCode != "" { if result, err := rt.JsCtx.Eval(plg.JsCode); err != nil { stack := GetJSError(err, plg.JsCode) rt.logger.Error(err.Error(), "stack", stack) } else { result.Free() } } } } } func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) { if r, err := rt.JsCtx.Eval(code); err == nil { result := MakeFromJsValue(r) r.Free() return result, nil, "" } else { // 检查错误 stack := GetJSError(err, code) rt.logger.Error(err.Error(), "stack", stack) return nil, err, stack } } func (rt *JSRuntime) Exec(code string) (err error, stack string) { rt.initCode(code) _, err, stack = rt.run(code) return err, stack } func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) { rt.initCode(code) return rt.run("(function(){" + code + "})()") } func SetPluginsConfig(conf map[string]plugin.Config) { for _, plg := range plugin.List() { if plg.Init != nil { plg.Init(conf[plg.Id]) } } } func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime { if logger == nil { logger = log.DefaultLogger } // 初始化JS虚拟机 jsRt := quickjs.NewRuntime() jsCtx := jsRt.NewContext() goCtx := plugin.NewContext(map[string]interface{}{ "*log.Logger": logger, "*quickjs.Context": jsCtx, }) rt := &JSRuntime{ freeJsValues: make([]quickjs.Value, 0), rt: jsRt, JsCtx: jsCtx, GoCtx: goCtx, logger: logger, plugins: map[string]*plugin.Plugin{}, } rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues) // 全局变量 if globals != nil { for k, obj := range globals { rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false)) } } // 注入 console rt.JsCtx.Globals().Set("console", MakeJsValue(rt.GoCtx, map[string]interface{}{ "log": func(args ...interface{}) { fmt.Println(makeStringArray(args, u.TextNone, u.BgNone)...) }, "info": func(args ...interface{}) { fmt.Println(makeStringArray(args, u.TextCyan, u.BgNone)...) }, "warn": func(args ...interface{}) { fmt.Println(makeStringArray(args, u.TextBlack, u.BgYellow)...) }, "error": func(args ...interface{}) { fmt.Println(makeStringArray(args, u.TextWhite, u.BgRed)...) }, }, false)) // 注入 logger rt.JsCtx.Globals().Set("logger", MakeJsValue(rt.GoCtx, map[string]interface{}{ "debug": func(message string, args *map[string]interface{}) { rt.logger.Debug(message, makeMapToArray(args)...) }, "info": func(message string, args *map[string]interface{}) { rt.logger.Info(message, makeMapToArray(args)...) }, "warn": func(message string, args *map[string]interface{}) { rt.logger.Warning(message, makeMapToArray(args)...) }, "error": func(message string, args *map[string]interface{}) { rt.logger.Error(message, makeMapToArray(args)...) }, }, false)) return rt } func makeMapToArray(args *map[string]interface{}) []interface{} { outArgs := make([]interface{}, 0) if args != nil { for k, v := range *args { outArgs = append(outArgs, k, v) } } return outArgs } func makeStringArray(args []interface{}, color u.TextColor, bg u.BgColor) []interface{} { stringArgs := make([]interface{}, len(args)) for i, v := range args { if color != u.TextNone || bg != u.BgNone { stringArgs[i] = u.Color(u.StringP(v), color, bg) } else { stringArgs[i] = u.StringP(v) } } return stringArgs } func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) { rt := New(globals, logger) defer func() { if err := recover(); err != nil { rt.logger.Error(u.String(err)) } rt.Close() }() return rt.Run(code) } var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`) func GetJSError(err error, code string) string { if err != nil { var jsErr *quickjs.Error if errors.As(err, &jsErr) { // 在错误信息中加入代码 codeLines := strings.Split(code, "\n") return jsErrorCodeMatcher.ReplaceAllStringFunc(jsErr.Stack, func(s2 string) string { errorLineNumber := u.Int(jsErrorCodeMatcher.FindStringSubmatch(s2)[1]) errorLine := "" if len(codeLines) >= errorLineNumber { errorLine = codeLines[errorLineNumber-1] } return s2 + " ```" + errorLine + "```" }) } else { return err.Error() } } else { return "" } }