Files
waf-platform/EdgeNode/internal/js/context.go
2026-02-04 20:27:13 +08:00

446 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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