This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,445 @@
// 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
}