// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. //go:build script package js import ( "errors" "fmt" "github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/files" "github.com/iwind/TeaGo/types" "math/big" "reflect" "regexp" "rogchap.com/v8go" "strings" "sync" ) var namespaceReg = regexp.MustCompile(`^[a-zA-Z0-9_.]+$`) type Context struct { rawContext *v8go.Context isolate *Isolate objectTemplate *ObjectTemplate goObjectId uint32 goObjectMap map[uint32]any goObjectLocker sync.RWMutex serverId int64 countUses int } func NewContext(rawContext *v8go.Context, isolate *Isolate) (*Context, error) { var ctx = &Context{ rawContext: rawContext, isolate: isolate, goObjectMap: map[uint32]any{}, } err := ctx.init() if err != nil { return nil, err } return ctx, nil } func (this *Context) init() error { this.objectTemplate = NewObjectTemplate(this) // 加载内置函数库 err := this.loadLibraries() if err != nil { return err } return nil } func (this *Context) Isolate() *Isolate { return this.isolate } func (this *Context) Run(source string, origin string) (*v8go.Value, error) { return this.rawContext.RunScript(source, origin) } func (this *Context) RunScript(script *Script) (*v8go.Value, error) { return script.rawScript.Run(this.rawContext) } func (this *Context) RawContext() *v8go.Context { return this.rawContext } func (this *Context) NewObjectTemplate() *ObjectTemplate { return NewObjectTemplate(this) } // NewValue 包装新的值 // TODO 支持[]byte func (this *Context) NewValue(value any) (*v8go.Value, error) { if value == nil { return nil, nil } switch x := value.(type) { case *v8go.Value: return x, nil case *v8go.Object: return x.Value, nil case int8: value = int32(x) case uint8: value = uint32(x) case int: value = int64(x) case uint: value = uint64(x) case int16: value = int32(x) case uint16: value = uint32(x) case string, int32, uint32, bool, int64, uint64, *big.Int: // 内置支持的类型,我们 DO NOTHING HERE default: var rv = reflect.ValueOf(value) switch rv.Kind() { case reflect.Slice: arrayValue, err := this.Run("new Array()", "") if err != nil { return nil, err } var obj = arrayValue.Object() err = obj.Set("length", uint32(rv.Len())) if err != nil { return nil, err } for i := 0; i < rv.Len(); i++ { var rvv = rv.Index(i) if !rvv.CanInterface() { continue } ev, err := this.NewValue(rvv.Interface()) if err != nil { return nil, err } err = obj.SetIdx(uint32(i), ev) if err != nil { return nil, err } } return obj.Value, nil case reflect.Map: obj, err := this.objectTemplate.NewInstance(this) if err != nil { return nil, err } for _, kv := range rv.MapKeys() { var rvv = rv.MapIndex(kv) if !rvv.CanInterface() { continue } ev, err := this.NewValue(rvv.Interface()) if err != nil { return nil, err } var kind = kv.Kind() // 支持map[any]Type if kind == reflect.Interface { kind = kv.Elem().Kind() kv = kv.Elem() } switch kind { case reflect.String: err = obj.Set(kv.String(), ev) if err != nil { return nil, err } case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64, reflect.Float32, reflect.Float64: err = obj.Set(types.String(kv.Interface()), ev) if err != nil { return nil, err } case reflect.Bool: if kv.Bool() { err = obj.Set("1", ev) } else { err = obj.Set("0", ev) } if err != nil { return nil, err } default: // 我们直接忽略那些类型不符的Key } } return obj.Value, nil case reflect.Struct: obj, err := this.objectTemplate.NewInstance(this) if err != nil { return nil, err } var rt = rv.Type() for i := 0; i < rt.NumField(); i++ { var field = rt.Field(i) if !field.IsExported() { continue } var jsonTag = field.Tag.Get("json") if len(jsonTag) == 0 { jsonTag = strings.ToLower(field.Name[:1]) + field.Name[1:] } var rFieldValue = rv.Field(i) if !rFieldValue.CanInterface() { continue } ev, err := this.NewValue(rFieldValue.Interface()) if err != nil { return nil, err } err = obj.Set(jsonTag, ev) if err != nil { return nil, err } } return obj.Value, nil case reflect.Ptr: rv = reflect.Indirect(rv) if rv.CanInterface() { return this.NewValue(rv.Interface()) } default: } } return v8go.NewValue(this.isolate.RawIsolate(), value) } func (this *Context) AddGoObject(obj any) (objectId uint32) { this.goObjectLocker.Lock() defer this.goObjectLocker.Unlock() this.goObjectId++ this.goObjectMap[this.goObjectId] = obj return this.goObjectId } func (this *Context) AddGoRequestObject(obj RequestInterface) (objectId uint32) { return this.AddGoObject(obj) } func (this *Context) AddGoResponseObject(obj ResponseInterface) (objectId uint32) { return this.AddGoObject(obj) } func (this *Context) GoObject(objectId uint32) (obj any, ok bool) { this.goObjectLocker.RLock() obj, ok = this.goObjectMap[objectId] this.goObjectLocker.RUnlock() return } func (this *Context) GoObjectMap() map[uint32]any { this.goObjectLocker.RLock() defer this.goObjectLocker.RUnlock() return this.goObjectMap } // Done 单次调用用Context之后,需要调用Done()来计数和清除绑定的系统资源 func (this *Context) Done() { this.countUses++ for _, library := range SharedLibraryManager.All() { library.JSDone(this) } this.goObjectLocker.Lock() this.goObjectMap = map[uint32]any{} this.goObjectId = 0 this.goObjectLocker.Unlock() } func (this *Context) Cleanup() { this.Done() } func (this *Context) Close() { this.Done() for _, library := range SharedLibraryManager.All() { library.JSDispose(this) } this.rawContext.Close() } func (this *Context) Global() *v8go.Object { return this.rawContext.Global() } func (this *Context) loadLibraries() error { // 运行公共函数脚本 _, err := this.Run(LibraryUtilsJavascript, "utils.js") if err != nil { return this.WrapJSErr(err) } // libraries for _, library := range SharedLibraryManager.All() { library.JSInit(this) // 准备命名空间 var namespace = library.JSNamespace() if !namespaceReg.MatchString(namespace) { return errors.New("load library failed: invalid namespace '" + namespace + "'") } if !strings.HasPrefix(namespace, "gojs.") { return errors.New("load library failed: namespace '" + namespace + "' must be started with prefix 'gojs.'") } _, err = this.Run("gojs.prepareNamespace(\""+namespace+"\")", "utils.js") if err != nil { return fmt.Errorf("prepare namespace '%s' failed: %w", namespace, err) } // 运行脚本 var prototypeJS = strings.ReplaceAll(library.JSPrototype(), "$this.", library.JSNamespace()+".") _, err = this.Run(prototypeJS, namespace+".js") if err != nil { return fmt.Errorf("run '%s' prototype failed: %w", namespace, this.WrapJSErr(err)) } // 增加函数 var reflectValue = reflect.ValueOf(library) libValue, err := this.Run(namespace, "utils.js") if err != nil { return fmt.Errorf("get namespace object '%s' failed: %w", namespace, err) } var libObject = libValue.Object() var argumentType = reflect.TypeOf(&FunctionArguments{}) for i := 0; i < reflectValue.NumMethod(); i++ { var method = reflectValue.Method(i) if !method.IsValid() { continue } var typeMethod = reflectValue.Type().Method(i) var methodName = typeMethod.Name if strings.HasPrefix(methodName, "JS") { continue } var methodType = typeMethod.Type if methodType.NumIn() != 2 /** this, arguments **/ || methodType.NumOut() != 2 /** result, error **/ { continue } if !methodType.In(1).AssignableTo(argumentType) { continue } if methodType.Out(1).Name() != "error" { continue } err = libObject.Set(methodName, v8go.NewFunctionTemplate(this.isolate.RawIsolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value { out := method.Call([]reflect.Value{reflect.ValueOf(NewFunctionArguments(this, info))}) if !out[1].IsNil() { errValue, _ := this.NewValue(out[1].Interface().(error).Error()) this.isolate.ThrowException(errValue) return nil } v, err := this.NewValue(out[0].Interface()) if err != nil { errValue, _ := this.NewValue(err.Error()) this.isolate.ThrowException(errValue) return nil } return v }).GetFunction(this.rawContext)) if err != nil { return err } } } // common scripts var commonScripts = sharedCommonScripts if len(commonScripts) > 0 { for _, commonScript := range commonScripts { _, err := this.Run(commonScript.Code, commonScript.Filename) if err != nil { remotelogs.Error("SCRIPT", "load common script '"+commonScript.Filename+"' failed: "+err.Error()) } } } // load common files from directory err = this.loadLibrariesFromDisk() if err != nil { remotelogs.Error("SCRIPT", err.Error()) } return nil } func (this *Context) WrapJSErr(err error) error { if err == nil { return nil } jsErr, ok := err.(*v8go.JSError) if !ok { return err } return fmt.Errorf("%w, location: %s", jsErr, jsErr.Location) } func (this *Context) SetServerId(serverId int64) { this.serverId = serverId } func (this *Context) ServerId() int64 { return this.serverId } func (this *Context) ReadLineFromLibrary(name string, line int) string { if line < 1 { return "" } for _, library := range SharedLibraryManager.All() { if library.JSNamespace() == name || library.JSNamespace()+".js" == name { var lines = strings.Split(library.JSPrototype(), "\n") if len(lines) >= line { return lines[line-1] } } } return "" } // 从硬盘中加载脚本 func (this *Context) loadLibrariesFromDisk() error { var dir = Tea.Root + "/scripts/js/" var jsFiles = files.NewFile(dir).List() for _, file := range jsFiles { if file.IsFile() && strings.HasSuffix(file.Name(), ".js") { s, err := file.ReadAllString() if err != nil { return fmt.Errorf("load local common script '%s' failed: %w", file.Path(), err) } _, err = this.Run(s, file.Name()) if err != nil { return fmt.Errorf("load local common script '%s' failed: %w", file.Path(), err) } } } return nil }