node build fixed
This commit is contained in:
33
seanime-2.9.10/internal/goja/goja_bindings/bindings.go
Normal file
33
seanime-2.9.10/internal/goja/goja_bindings/bindings.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func gojaValueIsDefined(v goja.Value) bool {
|
||||
return v != nil && !goja.IsUndefined(v) && !goja.IsNull(v)
|
||||
}
|
||||
|
||||
func NewErrorString(vm *goja.Runtime, err string) goja.Value {
|
||||
return vm.ToValue(vm.NewGoError(errors.New(err)))
|
||||
}
|
||||
|
||||
func NewError(vm *goja.Runtime, err error) goja.Value {
|
||||
return vm.ToValue(vm.NewGoError(err))
|
||||
}
|
||||
|
||||
func PanicThrowError(vm *goja.Runtime, err error) {
|
||||
goError := vm.NewGoError(err)
|
||||
panic(vm.ToValue(goError))
|
||||
}
|
||||
|
||||
func PanicThrowErrorString(vm *goja.Runtime, err string) {
|
||||
goError := vm.NewGoError(errors.New(err))
|
||||
panic(vm.ToValue(goError))
|
||||
}
|
||||
|
||||
func PanicThrowTypeError(vm *goja.Runtime, err string) {
|
||||
panic(vm.NewTypeError(err))
|
||||
}
|
||||
229
seanime-2.9.10/internal/goja/goja_bindings/common_test.go
Normal file
229
seanime-2.9.10/internal/goja/goja_bindings/common_test.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package goja_bindings_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"seanime/internal/extension"
|
||||
"seanime/internal/extension_repo"
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTestVM(t *testing.T) *goja.Runtime {
|
||||
vm := goja.New()
|
||||
vm.SetParserOptions(parser.WithDisableSourceMaps)
|
||||
// Bind the shared bindings
|
||||
extension_repo.ShareBinds(vm, util.NewLogger())
|
||||
fm := extension_repo.FieldMapper{}
|
||||
vm.SetFieldNameMapper(fm)
|
||||
return vm
|
||||
}
|
||||
|
||||
func divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("division by zero")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
func TestDivideFunction(t *testing.T) {
|
||||
vm := goja.New()
|
||||
vm.Set("divide", divide)
|
||||
|
||||
// Case 1: Successful division
|
||||
result, err := vm.RunString("divide(10, 3);")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3.3333333333333335, result.Export())
|
||||
|
||||
// Case 2: Division by zero should throw an exception
|
||||
_, err = vm.RunString("divide(10, 0);")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "division by zero")
|
||||
|
||||
// Case 3: Handling error with try-catch in JS
|
||||
result, err = vm.RunString(`
|
||||
try {
|
||||
divide(10, 0);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "GoError: division by zero", result.Export())
|
||||
}
|
||||
|
||||
func multipleReturns() (int, string, float64) {
|
||||
return 42, "hello", 3.14
|
||||
}
|
||||
|
||||
func TestMultipleReturns(t *testing.T) {
|
||||
vm := goja.New()
|
||||
vm.Set("multiReturn", multipleReturns)
|
||||
|
||||
v, err := vm.RunString("multiReturn();")
|
||||
assert.NoError(t, err)
|
||||
util.Spew(v.Export())
|
||||
}
|
||||
|
||||
func TestUserConfig(t *testing.T) {
|
||||
vm := setupTestVM(t)
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
ext := &extension.Extension{
|
||||
UserConfig: &extension.UserConfig{
|
||||
Fields: []extension.ConfigField{
|
||||
{
|
||||
Name: "test",
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Default: "Default value",
|
||||
},
|
||||
},
|
||||
},
|
||||
SavedUserConfig: &extension.SavedUserConfig{
|
||||
Values: map[string]string{
|
||||
"test": "Hello World!",
|
||||
},
|
||||
},
|
||||
}
|
||||
extension_repo.ShareBinds(vm, util.NewLogger())
|
||||
extension_repo.BindUserConfig(vm, ext, util.NewLogger())
|
||||
|
||||
vm.RunString(`
|
||||
const result = $getUserPreference("test");
|
||||
console.log(result);
|
||||
|
||||
const result2 = $getUserPreference("test2");
|
||||
console.log(result2);
|
||||
`)
|
||||
}
|
||||
|
||||
func TestByteSliceToUint8Array(t *testing.T) {
|
||||
// Initialize a new Goja VM
|
||||
vm := goja.New()
|
||||
|
||||
// Create a Go byte slice
|
||||
data := []byte("hello")
|
||||
|
||||
// Set the byte slice in the Goja VM
|
||||
vm.Set("data", data)
|
||||
|
||||
extension_repo.ShareBinds(vm, util.NewLogger())
|
||||
|
||||
// JavaScript code to verify the type and contents of 'data'
|
||||
jsCode := `
|
||||
console.log(typeof data, data);
|
||||
|
||||
const dataArrayBuffer = new ArrayBuffer(5);
|
||||
const uint8Array = new Uint8Array(dataArrayBuffer);
|
||||
uint8Array[0] = 104;
|
||||
uint8Array[1] = 101;
|
||||
uint8Array[2] = 108;
|
||||
uint8Array[3] = 108;
|
||||
uint8Array[4] = 111;
|
||||
console.log(typeof uint8Array, uint8Array);
|
||||
|
||||
console.log("toString", $toString(uint8Array));
|
||||
console.log("toString", uint8Array.toString());
|
||||
|
||||
|
||||
true; // Return true if all checks pass
|
||||
`
|
||||
|
||||
// Run the JavaScript code in the Goja VM
|
||||
result, err := vm.RunString(jsCode)
|
||||
if err != nil {
|
||||
t.Fatalf("JavaScript error: %v", err)
|
||||
}
|
||||
|
||||
// Assert that the result is true
|
||||
assert.Equal(t, true, result.Export())
|
||||
}
|
||||
|
||||
func TestGojaDocument(t *testing.T) {
|
||||
vm := setupTestVM(t)
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
tests := []struct {
|
||||
entry string
|
||||
}{
|
||||
{entry: "./js/test/doc-example.ts"},
|
||||
{entry: "./js/test/doc-example-2.ts"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.entry, func(t *testing.T) {
|
||||
fileB, err := os.ReadFile(tt.entry)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
source, err := extension_repo.JSVMTypescriptToJS(string(fileB))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = vm.RunString(source)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = vm.RunString(`function NewProvider() { return new Provider() }`)
|
||||
require.NoError(t, err)
|
||||
|
||||
newProviderFunc, ok := goja.AssertFunction(vm.Get("NewProvider"))
|
||||
require.True(t, ok)
|
||||
|
||||
classObjVal, err := newProviderFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
classObj := classObjVal.ToObject(vm)
|
||||
|
||||
testFunc, ok := goja.AssertFunction(classObj.Get("test"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := testFunc(classObj)
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateFulfilled {
|
||||
t.Logf("Fulfilled: %v", promise.Result())
|
||||
} else {
|
||||
t.Fatalf("Rejected: %v", promise.Result())
|
||||
}
|
||||
|
||||
fmt.Println(time.Since(now).Seconds())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalParams(t *testing.T) {
|
||||
vm := setupTestVM(t)
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
type Options struct {
|
||||
Add int `json:"add"`
|
||||
}
|
||||
|
||||
vm.Set("test", func(a int, opts Options) int {
|
||||
fmt.Println("opts", opts)
|
||||
return a + opts.Add
|
||||
})
|
||||
|
||||
vm.RunString(`
|
||||
const result = test(1);
|
||||
console.log(result);
|
||||
|
||||
const result2 = test(1, { add: 10 });
|
||||
console.log(result2);
|
||||
`)
|
||||
}
|
||||
200
seanime-2.9.10/internal/goja/goja_bindings/console.go
Normal file
200
seanime-2.9.10/internal/goja/goja_bindings/console.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"seanime/internal/events"
|
||||
"seanime/internal/extension"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/samber/mo"
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Console
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type console struct {
|
||||
logger *zerolog.Logger
|
||||
vm *goja.Runtime
|
||||
wsEventManager mo.Option[events.WSEventManagerInterface]
|
||||
ext *extension.Extension
|
||||
}
|
||||
|
||||
// BindConsole binds the console to the VM
|
||||
func BindConsole(vm *goja.Runtime, logger *zerolog.Logger) error {
|
||||
return BindConsoleWithWS(nil, vm, logger, nil)
|
||||
}
|
||||
|
||||
// BindConsoleWithWS binds the console to the VM and sends logs messages to the websocket manager
|
||||
// in order to be printed in the browser console
|
||||
func BindConsoleWithWS(ext *extension.Extension, vm *goja.Runtime, logger *zerolog.Logger, wsEventManager events.WSEventManagerInterface) error {
|
||||
c := &console{
|
||||
logger: logger,
|
||||
vm: vm,
|
||||
wsEventManager: mo.None[events.WSEventManagerInterface](),
|
||||
ext: ext,
|
||||
}
|
||||
if wsEventManager != nil {
|
||||
c.wsEventManager = mo.Some(wsEventManager)
|
||||
}
|
||||
|
||||
consoleObj := vm.NewObject()
|
||||
consoleObj.Set("log", c.logFunc("log"))
|
||||
consoleObj.Set("error", c.logFunc("error"))
|
||||
consoleObj.Set("warn", c.logFunc("warn"))
|
||||
consoleObj.Set("info", c.logFunc("info"))
|
||||
consoleObj.Set("debug", c.logFunc("debug"))
|
||||
|
||||
vm.Set("console", consoleObj)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *console) logFunc(t string) (ret func(c goja.FunctionCall) goja.Value) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.logger.Error().Msgf("extension: Panic from console: %v", r)
|
||||
ret = func(call goja.FunctionCall) goja.Value {
|
||||
return goja.Undefined()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
var ret []string
|
||||
for _, arg := range call.Arguments {
|
||||
// Check if the argument is a goja.Object
|
||||
if obj, ok := arg.(*goja.Object); ok {
|
||||
// First check if it's a Go error
|
||||
if _, ok := obj.Export().(error); ok {
|
||||
ret = append(ret, fmt.Sprintf("%+v", obj.Export()))
|
||||
continue
|
||||
}
|
||||
|
||||
// Then check if it's a JavaScript Error object by checking its constructor name
|
||||
constructor := obj.Get("constructor")
|
||||
if constructor != nil && !goja.IsUndefined(constructor) && !goja.IsNull(constructor) {
|
||||
if constructorObj, ok := constructor.(*goja.Object); ok {
|
||||
if name := constructorObj.Get("name"); name != nil && !goja.IsUndefined(name) && !goja.IsNull(name) {
|
||||
if name.String() == "Error" || strings.HasSuffix(name.String(), "Error") {
|
||||
message := obj.Get("message")
|
||||
stack := obj.Get("stack")
|
||||
errStr := name.String()
|
||||
if message != nil && !goja.IsUndefined(message) && !goja.IsNull(message) {
|
||||
errStr += ": " + fmt.Sprintf("%+v", message.Export())
|
||||
}
|
||||
if stack != nil && !goja.IsUndefined(stack) && !goja.IsNull(stack) {
|
||||
errStr += "\nStack: " + fmt.Sprintf("%+v", stack.Export())
|
||||
}
|
||||
ret = append(ret, errStr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for other objects: Try calling toString() if available
|
||||
if hasOwnPropFn, ok := goja.AssertFunction(obj.Get("hasOwnProperty")); ok {
|
||||
if retVal, err := hasOwnPropFn(obj, c.vm.ToValue("toString")); err == nil && retVal.ToBoolean() {
|
||||
tsVal := obj.Get("toString")
|
||||
if fn, ok := goja.AssertFunction(tsVal); ok {
|
||||
strVal, err := fn(obj)
|
||||
if err == nil {
|
||||
// Avoid double logging if toString() is just "[object Object]"
|
||||
if strVal.String() != "[object Object]" {
|
||||
ret = append(ret, strVal.String())
|
||||
continue // Skip default handling if toString() worked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Original default handling
|
||||
switch v := arg.Export().(type) {
|
||||
case nil:
|
||||
ret = append(ret, "undefined")
|
||||
case bool:
|
||||
ret = append(ret, fmt.Sprintf("%t", v))
|
||||
case int64, float64:
|
||||
ret = append(ret, fmt.Sprintf("%v", v))
|
||||
case string:
|
||||
if v == "" {
|
||||
ret = append(ret, fmt.Sprintf("%q", v))
|
||||
break
|
||||
}
|
||||
ret = append(ret, fmt.Sprintf("%s", v))
|
||||
case []byte:
|
||||
ret = append(ret, fmt.Sprintf("Uint8Array %s", fmt.Sprint(v)))
|
||||
case map[string]interface{}:
|
||||
// Try to marshal the value to JSON
|
||||
bs, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
ret = append(ret, fmt.Sprintf("%+v", v))
|
||||
} else {
|
||||
ret = append(ret, fmt.Sprintf("%s", string(bs)))
|
||||
}
|
||||
default:
|
||||
// Try to marshal the value to JSON
|
||||
bs, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
ret = append(ret, fmt.Sprintf("%+v", v))
|
||||
} else {
|
||||
ret = append(ret, fmt.Sprintf("%s", string(bs)))
|
||||
}
|
||||
}
|
||||
}
|
||||
switch t {
|
||||
case "log", "warn", "info", "debug":
|
||||
c.logger.Debug().Msgf("extension: (console.%s): %s", t, strings.Join(ret, " "))
|
||||
case "error":
|
||||
c.logger.Error().Msgf("extension: (console.error): %s", strings.Join(ret, " "))
|
||||
}
|
||||
if wsEventManager, found := c.wsEventManager.Get(); found && c.ext != nil {
|
||||
wsEventManager.SendEvent(events.ConsoleLog, fmt.Sprintf("%s (console.%s): %s", c.ext.ID, t, strings.Join(ret, " ")))
|
||||
}
|
||||
return goja.Undefined()
|
||||
}
|
||||
}
|
||||
|
||||
//func (c *console) logFunc(t string) (ret func(c goja.FunctionCall) goja.Value) {
|
||||
// defer func() {
|
||||
// if r := recover(); r != nil {
|
||||
// c.logger.Error().Msgf("extension: Panic from console: %v", r)
|
||||
// ret = func(call goja.FunctionCall) goja.Value {
|
||||
// return goja.Undefined()
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// return func(call goja.FunctionCall) goja.Value {
|
||||
// var ret []string
|
||||
// for _, arg := range call.Arguments {
|
||||
// if arg == nil || arg.Export() == nil || arg.ExportType() == nil {
|
||||
// ret = append(ret, "undefined")
|
||||
// continue
|
||||
// }
|
||||
// if bytes, ok := arg.Export().([]byte); ok {
|
||||
// ret = append(ret, fmt.Sprintf("%s", string(bytes)))
|
||||
// continue
|
||||
// }
|
||||
// if arg.ExportType().Kind() == reflect.Struct || arg.ExportType().Kind() == reflect.Map || arg.ExportType().Kind() == reflect.Slice {
|
||||
// ret = append(ret, strings.ReplaceAll(spew.Sprint(arg.Export()), "\n", ""))
|
||||
// } else {
|
||||
// ret = append(ret, fmt.Sprintf("%+v", arg.Export()))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// switch t {
|
||||
// case "log", "warn", "info", "debug":
|
||||
// c.logger.Debug().Msgf("extension: [console.%s] %s", t, strings.Join(ret, " "))
|
||||
// case "error":
|
||||
// c.logger.Error().Msgf("extension: [console.error] %s", strings.Join(ret, " "))
|
||||
// }
|
||||
// return goja.Undefined()
|
||||
// }
|
||||
//}
|
||||
330
seanime-2.9.10/internal/goja/goja_bindings/crypto.go
Normal file
330
seanime-2.9.10/internal/goja/goja_bindings/crypto.go
Normal file
@@ -0,0 +1,330 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type wordArray struct {
|
||||
vm *goja.Runtime
|
||||
iv []byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
func BindCrypto(vm *goja.Runtime) error {
|
||||
|
||||
err := vm.Set("CryptoJS", map[string]interface{}{
|
||||
"AES": map[string]interface{}{
|
||||
"encrypt": cryptoAESEncryptFunc(vm),
|
||||
"decrypt": cryptoAESDecryptFunc(vm),
|
||||
},
|
||||
"enc": map[string]interface{}{
|
||||
"Utf8": map[string]interface{}{
|
||||
"parse": cryptoEncUtf8ParseFunc(vm),
|
||||
"stringify": cryptoEncUtf8StringifyFunc(vm),
|
||||
},
|
||||
"Base64": map[string]interface{}{
|
||||
"parse": cryptoEncBase64ParseFunc(vm),
|
||||
"stringify": cryptoEncBase64StringifyFunc(vm),
|
||||
},
|
||||
"Hex": map[string]interface{}{
|
||||
"parse": cryptoEncHexParseFunc(vm),
|
||||
"stringify": cryptoEncHexStringifyFunc(vm),
|
||||
},
|
||||
"Latin1": map[string]interface{}{
|
||||
"parse": cryptoEncLatin1ParseFunc(vm),
|
||||
"stringify": cryptoEncLatin1StringifyFunc(vm),
|
||||
},
|
||||
"Utf16": map[string]interface{}{
|
||||
"parse": cryptoEncUtf16ParseFunc(vm),
|
||||
"stringify": cryptoEncUtf16StringifyFunc(vm),
|
||||
},
|
||||
"Utf16LE": map[string]interface{}{
|
||||
"parse": cryptoEncUtf16LEParseFunc(vm),
|
||||
"stringify": cryptoEncUtf16LEStringifyFunc(vm),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func newWordArrayGojaValue(vm *goja.Runtime, data []byte, iv []byte) goja.Value {
|
||||
wa := &wordArray{
|
||||
vm: vm,
|
||||
iv: iv,
|
||||
data: data,
|
||||
}
|
||||
// WordArray // Utf8
|
||||
// WordArray.toString(): string // Uses Base64.stringify
|
||||
// WordArray.toString(encoder: Encoder): string
|
||||
obj := vm.NewObject()
|
||||
obj.Prototype().Set("toString", wa.toStringFunc)
|
||||
obj.Set("toString", wa.toStringFunc)
|
||||
obj.Set("iv", iv)
|
||||
return obj
|
||||
}
|
||||
|
||||
func (wa *wordArray) toStringFunc(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) == 0 {
|
||||
return wa.vm.ToValue(base64Stringify(wa.data))
|
||||
}
|
||||
|
||||
encoder, ok := call.Argument(0).Export().(map[string]interface{})
|
||||
if !ok {
|
||||
panic(wa.vm.ToValue("TypeError: encoder parameter must be a CryptoJS.enc object"))
|
||||
}
|
||||
|
||||
var ret string
|
||||
if f, ok := encoder["stringify"]; ok {
|
||||
if stringify, ok := f.(func(functionCall goja.FunctionCall) goja.Value); ok {
|
||||
ret = stringify(goja.FunctionCall{Arguments: []goja.Value{wa.vm.ToValue(wa.data)}}).String()
|
||||
} else {
|
||||
panic(wa.vm.ToValue("TypeError: encoder.stringify must be a function"))
|
||||
}
|
||||
} else {
|
||||
ret = string(wa.data)
|
||||
}
|
||||
|
||||
return wa.vm.ToValue(ret)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// CryptoJS.AES.encrypt(message: string, key: string, cfg?: { iv: ArrayBuffer }): WordArray
|
||||
func cryptoAESEncryptFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) (ret goja.Value) {
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(vm.ToValue("TypeError: AES.encrypt requires at least 2 arguments"))
|
||||
}
|
||||
|
||||
message := call.Argument(0).String()
|
||||
|
||||
var keyBytes []byte
|
||||
switch call.Argument(1).Export().(type) {
|
||||
case string:
|
||||
key := call.Argument(1).String()
|
||||
keyBytes = adjustKeyLength([]byte(key))
|
||||
case []byte:
|
||||
keyBytes = call.Argument(1).Export().([]byte)
|
||||
keyBytes = adjustKeyLength(keyBytes)
|
||||
default:
|
||||
panic(vm.ToValue("TypeError: key parameter must be a string or an ArrayBuffer"))
|
||||
}
|
||||
|
||||
usedRandomIV := false
|
||||
// Check if IV is provided
|
||||
var ivBytes []byte
|
||||
if len(call.Arguments) > 2 {
|
||||
cfg := call.Argument(2).Export().(map[string]interface{})
|
||||
var ok bool
|
||||
iv, ok := cfg["iv"].([]byte)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: iv parameter must be an ArrayBuffer"))
|
||||
}
|
||||
ivBytes = iv
|
||||
if len(ivBytes) != aes.BlockSize {
|
||||
panic(vm.ToValue("TypeError: IV length must be equal to block size (16 bytes for AES)"))
|
||||
}
|
||||
} else {
|
||||
// Generate a random IV
|
||||
ivBytes = make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, ivBytes); err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to generate IV: %v", err)))
|
||||
}
|
||||
usedRandomIV = true
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = vm.ToValue(fmt.Sprintf("Encryption failed: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
// Encrypt the message
|
||||
encryptedMessage := encryptAES(vm, message, keyBytes, ivBytes)
|
||||
|
||||
if usedRandomIV {
|
||||
// Prepend the IV to the encrypted message
|
||||
encryptedMessage = append(ivBytes, encryptedMessage...)
|
||||
}
|
||||
|
||||
return newWordArrayGojaValue(vm, encryptedMessage, ivBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.AES.decrypt(encryptedMessage: string | WordArray, key: string, cfg?: { iv: ArrayBuffer }): WordArray
|
||||
func cryptoAESDecryptFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) (ret goja.Value) {
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(vm.ToValue("TypeError: AES.decrypt requires at least 2 arguments"))
|
||||
}
|
||||
|
||||
// Can be string or WordArray
|
||||
// If WordArray, String() will call WordArray.toString() which will return the base64 encoded string
|
||||
encryptedMessage := call.Argument(0).String()
|
||||
|
||||
var keyBytes []byte
|
||||
var originalPassword []byte
|
||||
switch call.Argument(1).Export().(type) {
|
||||
case string:
|
||||
key := call.Argument(1).String()
|
||||
originalPassword = []byte(key)
|
||||
keyBytes = adjustKeyLength([]byte(key))
|
||||
case []byte:
|
||||
keyBytes = call.Argument(1).Export().([]byte)
|
||||
originalPassword = keyBytes
|
||||
keyBytes = adjustKeyLength(keyBytes)
|
||||
default:
|
||||
panic(vm.ToValue("TypeError: key parameter must be a string or an ArrayBuffer"))
|
||||
}
|
||||
|
||||
var ivBytes []byte
|
||||
var cipherText []byte
|
||||
|
||||
// If IV is provided in the third argument
|
||||
if len(call.Arguments) > 2 {
|
||||
cfg := call.Argument(2).Export().(map[string]interface{})
|
||||
var ok bool
|
||||
iv, ok := cfg["iv"].([]byte)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: iv parameter must be an ArrayBuffer"))
|
||||
}
|
||||
ivBytes = iv
|
||||
if len(ivBytes) != aes.BlockSize {
|
||||
panic(vm.ToValue("TypeError: IV length must be equal to block size (16 bytes for AES)"))
|
||||
}
|
||||
var err error
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to decode ciphertext: %v", err)))
|
||||
}
|
||||
cipherText = decodedMessage
|
||||
} else {
|
||||
// Decode the base64 encoded string
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to decode ciphertext: %v", err)))
|
||||
}
|
||||
|
||||
// Check if openssl
|
||||
if len(decodedMessage) >= 16 && string(decodedMessage[:8]) == "Salted__" {
|
||||
salt := decodedMessage[8:16]
|
||||
cipherText = decodedMessage[16:]
|
||||
derivedKey, derivedIV := evpBytesToKey(originalPassword, salt, 32, aes.BlockSize)
|
||||
keyBytes = derivedKey
|
||||
ivBytes = derivedIV
|
||||
} else {
|
||||
// Extract the IV from the beginning of the message
|
||||
ivBytes = decodedMessage[:aes.BlockSize]
|
||||
cipherText = decodedMessage[aes.BlockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt the message
|
||||
decrypted := decryptAES(vm, cipherText, keyBytes, ivBytes)
|
||||
|
||||
return newWordArrayGojaValue(vm, decrypted, ivBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjusts the key length to match AES key length requirements (16, 24, or 32 bytes).
|
||||
// If the key length is not 16, 24, or 32 bytes, it is hashed using SHA-256 and truncated to 32 bytes (AES-256).
|
||||
func adjustKeyLength(keyBytes []byte) []byte {
|
||||
switch len(keyBytes) {
|
||||
case 16, 24, 32:
|
||||
// Valid AES key lengths: 16 bytes (AES-128), 24 bytes (AES-192), 32 bytes (AES-256)
|
||||
return keyBytes
|
||||
default:
|
||||
// Hash the key to 32 bytes (AES-256)
|
||||
hash := sha256.Sum256(keyBytes)
|
||||
return hash[:]
|
||||
}
|
||||
}
|
||||
|
||||
func encryptAES(vm *goja.Runtime, message string, key []byte, iv []byte) (ret []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = nil
|
||||
}
|
||||
}()
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("%v", err)))
|
||||
}
|
||||
|
||||
messageBytes := []byte(message)
|
||||
messageBytes = pkcs7Padding(messageBytes, aes.BlockSize)
|
||||
|
||||
cipherText := make([]byte, len(messageBytes))
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText, messageBytes)
|
||||
|
||||
return cipherText
|
||||
}
|
||||
|
||||
func decryptAES(vm *goja.Runtime, cipherText []byte, key []byte, iv []byte) (ret []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = nil
|
||||
}
|
||||
}()
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("%v", err)))
|
||||
}
|
||||
|
||||
plainText := make([]byte, len(cipherText))
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(plainText, cipherText)
|
||||
|
||||
plainText = pkcs7Trimming(plainText)
|
||||
|
||||
return plainText
|
||||
}
|
||||
|
||||
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs7Trimming(data []byte) []byte {
|
||||
length := len(data)
|
||||
up := int(data[length-1])
|
||||
return data[:(length - up)]
|
||||
}
|
||||
|
||||
func evpBytesToKey(password []byte, salt []byte, keyLen, ivLen int) ([]byte, []byte) {
|
||||
d := make([]byte, 0)
|
||||
dI := make([]byte, 0)
|
||||
|
||||
for len(d) < (keyLen + ivLen) {
|
||||
h := md5.New()
|
||||
h.Write(dI)
|
||||
h.Write(password)
|
||||
h.Write(salt)
|
||||
dI = h.Sum(nil)
|
||||
d = append(d, dI...)
|
||||
}
|
||||
|
||||
return d[:keyLen], d[keyLen : keyLen+ivLen]
|
||||
}
|
||||
296
seanime-2.9.10/internal/goja/goja_bindings/crypto_encoders.go
Normal file
296
seanime-2.9.10/internal/goja/goja_bindings/crypto_encoders.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"github.com/dop251/goja"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// UTF-8 Encode
|
||||
func utf8Parse(input string) []byte {
|
||||
return []byte(input)
|
||||
}
|
||||
|
||||
// UTF-8 Decode
|
||||
func utf8Stringify(input []byte) string {
|
||||
return string(input)
|
||||
}
|
||||
|
||||
// Base64 Encode
|
||||
func base64Parse(input string) []byte {
|
||||
data, err := base64.StdEncoding.DecodeString(input)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Base64 Decode
|
||||
func base64Stringify(input []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(input)
|
||||
}
|
||||
|
||||
// Hex Encode
|
||||
func hexParse(input string) []byte {
|
||||
data, err := hex.DecodeString(input)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Hex Decode
|
||||
func hexStringify(input []byte) string {
|
||||
return hex.EncodeToString(input)
|
||||
}
|
||||
|
||||
// Latin1 Encode
|
||||
func latin1Parse(input string) []byte {
|
||||
encoder := charmap.ISO8859_1.NewEncoder()
|
||||
data, _ := encoder.Bytes([]byte(input))
|
||||
return data
|
||||
}
|
||||
|
||||
// Latin1 Decode
|
||||
func latin1Stringify(input []byte) string {
|
||||
decoder := charmap.ISO8859_1.NewDecoder()
|
||||
data, _ := decoder.Bytes(input)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// UTF-16 Encode
|
||||
func utf16Parse(input string) []byte {
|
||||
encoded := utf16.Encode([]rune(input))
|
||||
result := make([]byte, len(encoded)*2)
|
||||
for i, val := range encoded {
|
||||
result[i*2] = byte(val >> 8)
|
||||
result[i*2+1] = byte(val)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UTF-16 Decode
|
||||
func utf16Stringify(input []byte) string {
|
||||
if len(input)%2 != 0 {
|
||||
return ""
|
||||
}
|
||||
decoded := make([]uint16, len(input)/2)
|
||||
for i := 0; i < len(decoded); i++ {
|
||||
decoded[i] = uint16(input[i*2])<<8 | uint16(input[i*2+1])
|
||||
}
|
||||
return string(utf16.Decode(decoded))
|
||||
}
|
||||
|
||||
// UTF-16LE Encode
|
||||
func utf16LEParse(input string) []byte {
|
||||
encoded := utf16.Encode([]rune(input))
|
||||
result := make([]byte, len(encoded)*2)
|
||||
for i, val := range encoded {
|
||||
result[i*2] = byte(val)
|
||||
result[i*2+1] = byte(val >> 8)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UTF-16LE Decode
|
||||
func utf16LEStringify(input []byte) string {
|
||||
if len(input)%2 != 0 {
|
||||
return ""
|
||||
}
|
||||
decoded := make([]uint16, len(input)/2)
|
||||
for i := 0; i < len(decoded); i++ {
|
||||
decoded[i] = uint16(input[i*2]) | uint16(input[i*2+1])<<8
|
||||
}
|
||||
return string(utf16.Decode(decoded))
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// CryptoJS.enc.Utf8.parse(input: string): WordArray
|
||||
func cryptoEncUtf8ParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf8.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(utf8Parse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Utf8.stringify(wordArray: WordArray): string
|
||||
func cryptoEncUtf8StringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf8.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(utf8Stringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Base64.parse(input: string): WordArray
|
||||
// e.g.
|
||||
func cryptoEncBase64ParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Base64.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(base64Parse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Base64.stringify(wordArray: WordArray): string
|
||||
func cryptoEncBase64StringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Base64.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(base64Stringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Hex.parse(input: string): WordArray
|
||||
func cryptoEncHexParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Hex.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(hexParse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Hex.stringify(wordArray: WordArray): string
|
||||
func cryptoEncHexStringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Hex.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(hexStringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Latin1.parse(input: string): WordArray
|
||||
func cryptoEncLatin1ParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Latin1.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(latin1Parse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Latin1.stringify(wordArray: WordArray): string
|
||||
func cryptoEncLatin1StringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Latin1.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(latin1Stringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Utf16.parse(input: string): WordArray
|
||||
func cryptoEncUtf16ParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf16.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(utf16Parse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Utf16.stringify(wordArray: WordArray): string
|
||||
func cryptoEncUtf16StringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf16.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(utf16Stringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Utf16LE.parse(input: string): WordArray
|
||||
func cryptoEncUtf16LEParseFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf16LE.parse requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val := call.Argument(0).String()
|
||||
return vm.ToValue(utf16LEParse(val))
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.enc.Utf16LE.stringify(wordArray: WordArray): string
|
||||
func cryptoEncUtf16LEStringifyFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.enc.Utf16LE.stringify requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().([]byte)
|
||||
if !ok {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
return vm.ToValue(utf16LEStringify(val))
|
||||
}
|
||||
}
|
||||
120
seanime-2.9.10/internal/goja/goja_bindings/crypto_hash.go
Normal file
120
seanime-2.9.10/internal/goja/goja_bindings/crypto_hash.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"github.com/dop251/goja"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// MD5 Hash
|
||||
func cryptoMD5Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.MD5 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hash := md5.Sum([]byte(val))
|
||||
return vm.ToValue(hash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// SHA1 Hash
|
||||
func cryptoSHA1Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.SHA1 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hash := sha1.Sum([]byte(val))
|
||||
return vm.ToValue(hash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// SHA256 Hash
|
||||
func cryptoSHA256Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.SHA256 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hash := sha256.Sum256([]byte(val))
|
||||
return vm.ToValue(hash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// SHA512 Hash
|
||||
func cryptoSHA512Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.SHA512 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hash := sha512.Sum512([]byte(val))
|
||||
return vm.ToValue(hash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// SHA3 Hash
|
||||
func cryptoSHA3Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.SHA3 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hash := sha3.Sum256([]byte(val))
|
||||
return vm.ToValue(hash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// RIPEMD-160 Hash
|
||||
func cryptoRIPEMD160Func(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: CryptoJS.RIPEMD160 requires at least 1 argument"))
|
||||
}
|
||||
if !gojaValueIsDefined(call.Arguments[0]) {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
val, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: argument is not a string"))
|
||||
}
|
||||
hasher := ripemd160.New()
|
||||
hasher.Write([]byte(val))
|
||||
return vm.ToValue(hasher.Sum(nil))
|
||||
}
|
||||
}
|
||||
194
seanime-2.9.10/internal/goja/goja_bindings/crypto_test.go
Normal file
194
seanime-2.9.10/internal/goja/goja_bindings/crypto_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
gojabuffer "github.com/dop251/goja_nodejs/buffer"
|
||||
gojarequire "github.com/dop251/goja_nodejs/require"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGojaCrypto(t *testing.T) {
|
||||
vm := goja.New()
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindCrypto(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
|
||||
try {
|
||||
|
||||
console.log("\nTesting Buffer encoding/decoding")
|
||||
|
||||
const originalString = "Hello, this is a string to encode!"
|
||||
const base64String = Buffer.from(originalString).toString("base64")
|
||||
|
||||
console.log("Original String:", originalString)
|
||||
console.log("Base64 Encoded:", base64String)
|
||||
|
||||
const decodedString = Buffer.from(base64String, "base64").toString("utf-8")
|
||||
|
||||
console.log("Base64 Decoded:", decodedString)
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
console.log("\nTesting AES")
|
||||
|
||||
let message = "seanime"
|
||||
let key = CryptoJS.enc.Utf8.parse("secret key")
|
||||
|
||||
|
||||
console.log("Message:", message)
|
||||
|
||||
let encrypted = CryptoJS.AES.encrypt(message, key)
|
||||
console.log("Encrypted without IV:", encrypted) // map[iv toString]
|
||||
console.log("Encrypted.toString():", encrypted.toString()) // AoHrnhJfbRht2idLHM82WdkIEpRbXufnA6+ozty9fbk=
|
||||
console.log("Encrypted.toString(CryptoJS.enc.Base64):", encrypted.toString(CryptoJS.enc.Base64)) // AoHrnhJfbRht2idLHM82WdkIEpRbXufnA6+ozty9fbk=
|
||||
|
||||
let decrypted = CryptoJS.AES.decrypt(encrypted, key)
|
||||
console.log("Decrypted:", decrypted.toString(CryptoJS.enc.Utf8))
|
||||
|
||||
let iv = CryptoJS.enc.Utf8.parse("3134003223491201")
|
||||
encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv })
|
||||
console.log("Encrypted with IV:", encrypted) // map[iv toString]
|
||||
|
||||
decrypted = CryptoJS.AES.decrypt(encrypted, key)
|
||||
console.log("Decrypted without IV:", decrypted.toString(CryptoJS.enc.Utf8))
|
||||
|
||||
decrypted = CryptoJS.AES.decrypt(encrypted, key, { iv: iv })
|
||||
console.log("Decrypted with IV:", decrypted.toString(CryptoJS.enc.Utf8)) // seanime
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
console.log("\nTesting encoders")
|
||||
|
||||
console.log("")
|
||||
let a = CryptoJS.enc.Utf8.parse("Hello, World!")
|
||||
console.log("Base64 Parsed:", a)
|
||||
let b = CryptoJS.enc.Base64.stringify(a)
|
||||
console.log("Base64 Stringified:", b)
|
||||
let c = CryptoJS.enc.Base64.parse(b)
|
||||
console.log("Base64 Parsed:", c)
|
||||
let d = CryptoJS.enc.Utf8.stringify(c)
|
||||
console.log("Base64 Stringified:", d)
|
||||
console.log("")
|
||||
|
||||
let words = CryptoJS.enc.Latin1.parse("Hello, World!")
|
||||
console.log("Latin1 Parsed:", words)
|
||||
let latin1 = CryptoJS.enc.Latin1.stringify(words)
|
||||
console.log("Latin1 Stringified", latin1)
|
||||
|
||||
words = CryptoJS.enc.Hex.parse("48656c6c6f2c20576f726c6421")
|
||||
console.log("Hex Parsed:", words)
|
||||
let hex = CryptoJS.enc.Hex.stringify(words)
|
||||
console.log("Hex Stringified", hex)
|
||||
|
||||
words = CryptoJS.enc.Utf8.parse("")
|
||||
console.log("Utf8 Parsed:", words)
|
||||
let utf8 = CryptoJS.enc.Utf8.stringify(words)
|
||||
console.log("Utf8 Stringified", utf8)
|
||||
|
||||
words = CryptoJS.enc.Utf16.parse("Hello, World!")
|
||||
console.log("Utf16 Parsed:", words)
|
||||
let utf16 = CryptoJS.enc.Utf16.stringify(words)
|
||||
console.log("Utf16 Stringified", utf16)
|
||||
|
||||
words = CryptoJS.enc.Utf16LE.parse("Hello, World!")
|
||||
console.log("Utf16LE Parsed:", words)
|
||||
utf16 = CryptoJS.enc.Utf16LE.stringify(words)
|
||||
console.log("Utf16LE Stringified", utf16)
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error:", e)
|
||||
}
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateRejected {
|
||||
err := promise.Result()
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGojaCryptoOpenSSL(t *testing.T) {
|
||||
vm := goja.New()
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindCrypto(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
|
||||
try {
|
||||
|
||||
console.log("\nTesting Buffer encoding/decoding")
|
||||
|
||||
const payload = "U2FsdGVkX19ZanX9W5jQGgNGOIOBGxhY6gxa1EHnRi3yHL8Ml4cMmQeryf9p04N12VuOjiBas21AcU0Ypc4dB4AWOdc9Cn1wdA2DuQhryUonKYHwV/XXJ53DBn1OIqAvrIAxrN8S2j9Rk5z/F/peu1Kk/d3m82jiKvhTWQcxDeDW8UzCMZbbFnm4qJC3k19+PD5Pal5sBcVTGRXNCpvSSpYb56FcP9Xs+3DyBWhNUqJuO+Wwm3G1J5HhklxCWZ7tcn7TE5Y8d5ORND7t51Padrw4LgEOootqHtfHuBVX6EqlvJslXt0kFgcXJUIO+hw0q5SJ+tiS7o/2OShJ7BCk4XzfQmhFJdBJYGjQ8WPMHYzLuMzDkf6zk2+m7YQtUTXx8SVoLXFOt8gNZeD942snGrWA5+CdYveOfJ8Yv7owoOueMzzYqr5rzG7GVapVI0HzrA24LR4AjRDICqTsJEy6Yg=="
|
||||
const key = "6315b93606d60f48c964b67b14701f3848ef25af01296cf7e6a98c9460e1d2ac"
|
||||
console.log("Original String:", payload)
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(payload, key)
|
||||
|
||||
console.log("Decrypted:", decrypted.toString(CryptoJS.enc.Utf8))
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateRejected {
|
||||
err := promise.Result()
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
679
seanime-2.9.10/internal/goja/goja_bindings/document.go
Normal file
679
seanime-2.9.10/internal/goja/goja_bindings/document.go
Normal file
@@ -0,0 +1,679 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/dop251/goja"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type doc struct {
|
||||
vm *goja.Runtime
|
||||
doc *goquery.Document
|
||||
docSelection *docSelection
|
||||
}
|
||||
|
||||
type docSelection struct {
|
||||
doc *doc
|
||||
selection *goquery.Selection
|
||||
}
|
||||
|
||||
func setSelectionObjectProperties(obj *goja.Object, docS *docSelection) {
|
||||
_ = obj.Set("length", docS.Length)
|
||||
_ = obj.Set("html", docS.Html)
|
||||
_ = obj.Set("text", docS.Text)
|
||||
_ = obj.Set("attr", docS.Attr)
|
||||
_ = obj.Set("find", docS.Find)
|
||||
_ = obj.Set("children", docS.Children)
|
||||
_ = obj.Set("each", docS.Each)
|
||||
_ = obj.Set("text", docS.Text)
|
||||
_ = obj.Set("parent", docS.Parent)
|
||||
_ = obj.Set("parentsUntil", docS.ParentsUntil)
|
||||
_ = obj.Set("parents", docS.Parents)
|
||||
_ = obj.Set("end", docS.End)
|
||||
_ = obj.Set("closest", docS.Closest)
|
||||
_ = obj.Set("map", docS.Map)
|
||||
_ = obj.Set("first", docS.First)
|
||||
_ = obj.Set("last", docS.Last)
|
||||
_ = obj.Set("eq", docS.Eq)
|
||||
_ = obj.Set("contents", docS.Contents)
|
||||
_ = obj.Set("contentsFiltered", docS.ContentsFiltered)
|
||||
_ = obj.Set("filter", docS.Filter)
|
||||
_ = obj.Set("not", docS.Not)
|
||||
_ = obj.Set("is", docS.Is)
|
||||
_ = obj.Set("has", docS.Has)
|
||||
_ = obj.Set("next", docS.Next)
|
||||
_ = obj.Set("nextAll", docS.NextAll)
|
||||
_ = obj.Set("nextUntil", docS.NextUntil)
|
||||
_ = obj.Set("prev", docS.Prev)
|
||||
_ = obj.Set("prevAll", docS.PrevAll)
|
||||
_ = obj.Set("prevUntil", docS.PrevUntil)
|
||||
_ = obj.Set("siblings", docS.Siblings)
|
||||
_ = obj.Set("data", docS.Data)
|
||||
_ = obj.Set("attrs", docS.Attrs)
|
||||
}
|
||||
|
||||
func BindDocument(vm *goja.Runtime) error {
|
||||
// Set Doc "class"
|
||||
err := vm.Set("Doc", func(call goja.ConstructorCall) *goja.Object {
|
||||
obj := call.This
|
||||
if len(call.Arguments) != 1 {
|
||||
return goja.Undefined().ToObject(vm)
|
||||
}
|
||||
html := call.Arguments[0].String()
|
||||
|
||||
goqueryDoc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
return goja.Undefined().ToObject(vm)
|
||||
}
|
||||
d := &doc{
|
||||
vm: vm,
|
||||
doc: goqueryDoc,
|
||||
docSelection: &docSelection{
|
||||
doc: nil,
|
||||
selection: goqueryDoc.Selection,
|
||||
},
|
||||
}
|
||||
d.docSelection.doc = d
|
||||
|
||||
setSelectionObjectProperties(obj, d.docSelection)
|
||||
return obj
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set "LoadDoc" function
|
||||
err = vm.Set("LoadDoc", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
panic(vm.ToValue("missing argument"))
|
||||
}
|
||||
|
||||
html := call.Arguments[0].String()
|
||||
goqueryDoc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
return goja.Null()
|
||||
}
|
||||
|
||||
d := &doc{
|
||||
vm: vm,
|
||||
doc: goqueryDoc,
|
||||
docSelection: &docSelection{
|
||||
doc: nil,
|
||||
selection: goqueryDoc.Selection,
|
||||
},
|
||||
}
|
||||
d.docSelection.doc = d
|
||||
|
||||
docSelectionFunction := func(call goja.FunctionCall) goja.Value {
|
||||
selectorStr, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.NewTypeError("argument is not a string").ToString())
|
||||
}
|
||||
return newDocSelectionGojaValue(d, d.doc.Find(selectorStr))
|
||||
}
|
||||
|
||||
return vm.ToValue(docSelectionFunction)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Document
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func newDocSelectionGojaValue(d *doc, selection *goquery.Selection) goja.Value {
|
||||
ds := &docSelection{
|
||||
doc: d,
|
||||
selection: selection,
|
||||
}
|
||||
|
||||
obj := d.vm.NewObject()
|
||||
setSelectionObjectProperties(obj, ds)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Selection
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (s *docSelection) getFirstStringArg(call goja.FunctionCall) string {
|
||||
selectorStr, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("argument is not a string").ToString())
|
||||
}
|
||||
return selectorStr
|
||||
}
|
||||
|
||||
func (s *docSelection) Length(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
return s.doc.vm.ToValue(0)
|
||||
}
|
||||
return s.doc.vm.ToValue(s.selection.Length())
|
||||
}
|
||||
|
||||
// Find gets the descendants of each element in the current set of matched elements, filtered by a selector.
|
||||
//
|
||||
// find(selector: string): DocSelection;
|
||||
func (s *docSelection) Find(call goja.FunctionCall) (ret goja.Value) {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Find(selectorStr))
|
||||
}
|
||||
|
||||
func (s *docSelection) Html(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
return goja.Null()
|
||||
}
|
||||
htmlStr, err := s.selection.Html()
|
||||
if err != nil {
|
||||
return goja.Null()
|
||||
}
|
||||
return s.doc.vm.ToValue(htmlStr)
|
||||
}
|
||||
|
||||
func (s *docSelection) Text(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
return s.doc.vm.ToValue("")
|
||||
}
|
||||
return s.doc.vm.ToValue(s.selection.Text())
|
||||
}
|
||||
|
||||
// Attr gets the specified attribute's value for the first element in the Selection. To get the value for each element individually, use a
|
||||
// looping construct such as Each or Map method.
|
||||
//
|
||||
// attr(name: string): string | undefined;
|
||||
func (s *docSelection) Attr(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
attr, found := s.selection.Attr(s.getFirstStringArg(call))
|
||||
if !found {
|
||||
return goja.Undefined()
|
||||
}
|
||||
return s.doc.vm.ToValue(attr)
|
||||
}
|
||||
|
||||
// Attrs gets all attributes for the first element in the Selection.
|
||||
//
|
||||
// attrs(): { [key: string]: string };
|
||||
func (s *docSelection) Attrs(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
attrs := make(map[string]string)
|
||||
for _, v := range s.selection.Get(0).Attr {
|
||||
attrs[v.Key] = v.Val
|
||||
}
|
||||
return s.doc.vm.ToValue(attrs)
|
||||
}
|
||||
|
||||
// Data gets data associated with the matched elements or return the value at the named data store for the first element in the set of matched elements.
|
||||
//
|
||||
// data(name?: string): { [key: string]: string } | string | undefined;
|
||||
func (s *docSelection) Data(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
var data map[string]string
|
||||
n := s.selection.Get(0)
|
||||
if n == nil {
|
||||
return goja.Undefined()
|
||||
}
|
||||
for _, v := range n.Attr {
|
||||
if strings.HasPrefix(v.Key, "data-") {
|
||||
if data == nil {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
data[v.Key] = v.Val
|
||||
}
|
||||
}
|
||||
return s.doc.vm.ToValue(data)
|
||||
}
|
||||
|
||||
name := call.Argument(0).String()
|
||||
n := s.selection.Get(0)
|
||||
if n == nil {
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
data, found := s.selection.Attr(fmt.Sprintf("data-%s", name))
|
||||
if !found {
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
return s.doc.vm.ToValue(data)
|
||||
}
|
||||
|
||||
// Parent gets the parent of each element in the Selection. It returns a new Selection object containing the matched elements.
|
||||
//
|
||||
// parent(selector?: string): DocSelection;
|
||||
func (s *docSelection) Parent(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Parent())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ParentFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// Parents gets the ancestors of each element in the current Selection. It returns a new Selection object with the matched elements.
|
||||
//
|
||||
// parents(selector?: string): DocSelection;
|
||||
func (s *docSelection) Parents(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Parents())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ParentsFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// ParentsUntil gets the ancestors of each element in the Selection, up to but not including the element matched by the selector. It returns a
|
||||
// new Selection object containing the matched elements.
|
||||
//
|
||||
// parentsUntil(selector?: string, until?: string): DocSelection;
|
||||
func (s *docSelection) ParentsUntil(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
if len(call.Arguments) < 2 {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ParentsUntil(selectorStr))
|
||||
}
|
||||
untilStr := call.Argument(1).String()
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ParentsFilteredUntil(selectorStr, untilStr))
|
||||
}
|
||||
|
||||
// End ends the most recent filtering operation in the current chain and returns the set of matched elements to its previous state.
|
||||
//
|
||||
// end(): DocSelection;
|
||||
func (s *docSelection) End(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.End())
|
||||
}
|
||||
|
||||
// Closest gets the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
|
||||
//
|
||||
// closest(selector?: string): DocSelection;
|
||||
func (s *docSelection) Closest(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Closest(""))
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Closest(selectorStr))
|
||||
}
|
||||
|
||||
// Next gets the next sibling of each selected element, optionally filtered by a selector.
|
||||
//
|
||||
// next(selector?: string): DocSelection;
|
||||
func (s *docSelection) Next(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Next())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NextFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// NextAll gets all following siblings of each element in the Selection, optionally filtered by a selector.
|
||||
//
|
||||
// nextAll(selector?: string): DocSelection;
|
||||
func (s *docSelection) NextAll(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NextAll())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NextAllFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// NextUntil gets all following siblings of each element up to but not including the element matched by the selector.
|
||||
//
|
||||
// nextUntil(selector: string, until?: string): DocSelection;
|
||||
func (s *docSelection) NextUntil(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
if len(call.Arguments) < 2 {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NextUntil(selectorStr))
|
||||
}
|
||||
untilStr := call.Argument(1).String()
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NextFilteredUntil(selectorStr, untilStr))
|
||||
}
|
||||
|
||||
// Prev gets the previous sibling of each selected element optionally filtered by a selector.
|
||||
//
|
||||
// prev(selector?: string): DocSelection;
|
||||
func (s *docSelection) Prev(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Prev())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.PrevFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// PrevAll gets all preceding siblings of each element in the Selection, optionally filtered by a selector.
|
||||
//
|
||||
// prevAll(selector?: string): DocSelection;
|
||||
func (s *docSelection) PrevAll(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.PrevAll())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.PrevAllFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// PrevUntil gets all preceding siblings of each element up to but not including the element matched by the selector.
|
||||
//
|
||||
// prevUntil(selector: string, until?: string): DocSelection;
|
||||
func (s *docSelection) PrevUntil(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
if len(call.Arguments) < 2 {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.PrevUntil(selectorStr))
|
||||
}
|
||||
untilStr := call.Argument(1).String()
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.PrevFilteredUntil(selectorStr, untilStr))
|
||||
}
|
||||
|
||||
// Siblings gets the siblings of each element (excluding the element) in the set of matched elements, optionally filtered by a selector.
|
||||
//
|
||||
// siblings(selector?: string): DocSelection;
|
||||
func (s *docSelection) Siblings(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Siblings())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.SiblingsFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// Children gets the element children of each element in the set of matched elements.
|
||||
//
|
||||
// children(selector?: string): DocSelection;
|
||||
func (s *docSelection) Children(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Children())
|
||||
}
|
||||
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ChildrenFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// Contents gets the children of each element in the Selection, including text and comment nodes. It returns a new Selection object containing
|
||||
// these elements.
|
||||
//
|
||||
// contents(): DocSelection;
|
||||
func (s *docSelection) Contents(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Contents())
|
||||
}
|
||||
|
||||
// ContentsFiltered gets the children of each element in the Selection, filtered by the specified selector. It returns a new Selection object
|
||||
// containing these elements. Since selectors only act on Element nodes, this function is an alias to ChildrenFiltered unless the selector is
|
||||
// empty, in which case it is an alias to Contents.
|
||||
//
|
||||
// contentsFiltered(selector: string): DocSelection;
|
||||
func (s *docSelection) ContentsFiltered(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.ContentsFiltered(selectorStr))
|
||||
}
|
||||
|
||||
// Filter reduces the set of matched elements to those that match the selector string. It returns a new Selection object for this subset of
|
||||
// matching elements.
|
||||
//
|
||||
// filter(selector: string | (index: number, element: DocSelection) => boolean): DocSelection;
|
||||
func (s *docSelection) Filter(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
panic(s.doc.vm.ToValue("missing argument"))
|
||||
}
|
||||
|
||||
switch call.Argument(0).Export().(type) {
|
||||
case string:
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Filter(selectorStr))
|
||||
|
||||
case func(call goja.FunctionCall) goja.Value:
|
||||
callback := call.Argument(0).Export().(func(call goja.FunctionCall) goja.Value)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.FilterFunction(func(i int, selection *goquery.Selection) bool {
|
||||
ret, ok := callback(goja.FunctionCall{Arguments: []goja.Value{
|
||||
s.doc.vm.ToValue(i),
|
||||
newDocSelectionGojaValue(s.doc, selection),
|
||||
}}).Export().(bool)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("callback did not return a boolean").ToString())
|
||||
}
|
||||
return ret
|
||||
}))
|
||||
default:
|
||||
panic(s.doc.vm.NewTypeError("argument is not a string or function").ToString())
|
||||
}
|
||||
}
|
||||
|
||||
// Not removes elements from the Selection that match the selector string. It returns a new Selection object with the matching elements removed.
|
||||
//
|
||||
// not(selector: string | (index: number, element: DocSelection) => boolean): DocSelection;
|
||||
func (s *docSelection) Not(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
panic(s.doc.vm.ToValue("missing argument"))
|
||||
}
|
||||
|
||||
switch call.Argument(0).Export().(type) {
|
||||
case string:
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Not(selectorStr))
|
||||
case func(call goja.FunctionCall) goja.Value:
|
||||
callback := call.Argument(0).Export().(func(call goja.FunctionCall) goja.Value)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.NotFunction(func(i int, selection *goquery.Selection) bool {
|
||||
ret, ok := callback(goja.FunctionCall{Arguments: []goja.Value{
|
||||
s.doc.vm.ToValue(i),
|
||||
newDocSelectionGojaValue(s.doc, selection),
|
||||
}}).Export().(bool)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("callback did not return a boolean").ToString())
|
||||
}
|
||||
return ret
|
||||
}))
|
||||
default:
|
||||
panic(s.doc.vm.NewTypeError("argument is not a string or function").ToString())
|
||||
}
|
||||
}
|
||||
|
||||
// Is checks the current matched set of elements against a selector and returns true if at least one of these elements matches.
|
||||
//
|
||||
// is(selector: string | (index: number, element: DocSelection) => boolean): boolean;
|
||||
func (s *docSelection) Is(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
|
||||
if len(call.Arguments) == 0 || !gojaValueIsDefined(call.Argument(0)) {
|
||||
panic(s.doc.vm.ToValue("missing argument"))
|
||||
}
|
||||
|
||||
switch call.Argument(0).Export().(type) {
|
||||
case string:
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return s.doc.vm.ToValue(s.selection.Is(selectorStr))
|
||||
case func(call goja.FunctionCall) goja.Value:
|
||||
callback := call.Argument(0).Export().(func(call goja.FunctionCall) goja.Value)
|
||||
return s.doc.vm.ToValue(s.selection.IsFunction(func(i int, selection *goquery.Selection) bool {
|
||||
ret, ok := callback(goja.FunctionCall{Arguments: []goja.Value{
|
||||
s.doc.vm.ToValue(i),
|
||||
newDocSelectionGojaValue(s.doc, selection),
|
||||
}}).Export().(bool)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("callback did not return a boolean").ToString())
|
||||
}
|
||||
return ret
|
||||
}))
|
||||
default:
|
||||
panic(s.doc.vm.NewTypeError("argument is not a string or function").ToString())
|
||||
}
|
||||
}
|
||||
|
||||
// Has reduces the set of matched elements to those that have a descendant that matches the selector. It returns a new Selection object with the
|
||||
// matching elements.
|
||||
//
|
||||
// has(selector: string): DocSelection;
|
||||
func (s *docSelection) Has(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
selectorStr := s.getFirstStringArg(call)
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Has(selectorStr))
|
||||
}
|
||||
|
||||
// Each iterates over a Selection object, executing a function for each matched element. It returns the current Selection object. The function f
|
||||
// is called for each element in the selection with the index of the element in that selection starting at 0, and a *Selection that contains only
|
||||
// that element.
|
||||
//
|
||||
// each(callback: (index: number, element: DocSelection) => void): DocSelection;
|
||||
func (s *docSelection) Each(call goja.FunctionCall) (ret goja.Value) {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
callback, ok := call.Argument(0).Export().(func(call goja.FunctionCall) goja.Value)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("argument is not a function").ToString())
|
||||
}
|
||||
s.selection.Each(func(i int, selection *goquery.Selection) {
|
||||
callback(goja.FunctionCall{Arguments: []goja.Value{
|
||||
s.doc.vm.ToValue(i),
|
||||
newDocSelectionGojaValue(s.doc, selection),
|
||||
}})
|
||||
})
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
// Map passes each element in the current matched set through a function, producing a slice of string holding the returned values. The function f
|
||||
// is called for each element in the selection with the index of the element in that selection starting at 0, and a *Selection that contains only
|
||||
// that element.
|
||||
//
|
||||
// map(callback: (index: number, element: DocSelection) => DocSelection): DocSelection[];
|
||||
func (s *docSelection) Map(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
callback, ok := call.Argument(0).Export().(func(call goja.FunctionCall) goja.Value)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("argument is not a function").ToString())
|
||||
}
|
||||
var retStr []interface{}
|
||||
var retDocSelection map[string]interface{}
|
||||
s.selection.Each(func(i int, selection *goquery.Selection) {
|
||||
val := callback(goja.FunctionCall{Arguments: []goja.Value{
|
||||
s.doc.vm.ToValue(i),
|
||||
newDocSelectionGojaValue(s.doc, selection),
|
||||
}})
|
||||
|
||||
if valExport, ok := val.Export().(map[string]interface{}); ok {
|
||||
retDocSelection = valExport
|
||||
}
|
||||
retStr = append(retStr, val.Export())
|
||||
|
||||
})
|
||||
if len(retStr) > 0 {
|
||||
return s.doc.vm.ToValue(retStr)
|
||||
}
|
||||
return s.doc.vm.ToValue(retDocSelection)
|
||||
}
|
||||
|
||||
// First reduces the set of matched elements to the first in the set. It returns a new Selection object, and an empty Selection object if the
|
||||
// selection is empty.
|
||||
//
|
||||
// first(): DocSelection;
|
||||
func (s *docSelection) First(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.First())
|
||||
}
|
||||
|
||||
// Last reduces the set of matched elements to the last in the set. It returns a new Selection object, and an empty Selection object if the
|
||||
// selection is empty.
|
||||
//
|
||||
// last(): DocSelection;
|
||||
func (s *docSelection) Last(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Last())
|
||||
}
|
||||
|
||||
// Eq reduces the set of matched elements to the one at the specified index. If a negative index is given, it counts backwards starting at the
|
||||
// end of the set. It returns a new Selection object, and an empty Selection object if the index is invalid.
|
||||
//
|
||||
// eq(index: number): DocSelection;
|
||||
func (s *docSelection) Eq(call goja.FunctionCall) goja.Value {
|
||||
if s.selection == nil {
|
||||
panic(s.doc.vm.ToValue("selection is nil"))
|
||||
}
|
||||
index, ok := call.Argument(0).Export().(int64)
|
||||
if !ok {
|
||||
panic(s.doc.vm.NewTypeError("argument is not a number").String())
|
||||
}
|
||||
return newDocSelectionGojaValue(s.doc, s.selection.Eq(int(index)))
|
||||
}
|
||||
307
seanime-2.9.10/internal/goja/goja_bindings/fetch.go
Normal file
307
seanime-2.9.10/internal/goja/goja_bindings/fetch.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
maxConcurrentRequests = 50
|
||||
defaultTimeout = 35 * time.Second
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Fetch
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var (
|
||||
clientWithCloudFlareBypass = req.C().
|
||||
SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36").
|
||||
SetTimeout(defaultTimeout).
|
||||
EnableInsecureSkipVerify().
|
||||
ImpersonateChrome()
|
||||
|
||||
clientWithoutBypass = req.C().
|
||||
SetTimeout(defaultTimeout)
|
||||
)
|
||||
|
||||
type Fetch struct {
|
||||
vm *goja.Runtime
|
||||
fetchSem chan struct{}
|
||||
vmResponseCh chan func()
|
||||
}
|
||||
|
||||
func NewFetch(vm *goja.Runtime) *Fetch {
|
||||
return &Fetch{
|
||||
vm: vm,
|
||||
fetchSem: make(chan struct{}, maxConcurrentRequests),
|
||||
vmResponseCh: make(chan func(), maxConcurrentRequests),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fetch) ResponseChannel() <-chan func() {
|
||||
return f.vmResponseCh
|
||||
}
|
||||
|
||||
func (f *Fetch) Close() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
}
|
||||
}()
|
||||
close(f.vmResponseCh)
|
||||
}
|
||||
|
||||
type fetchOptions struct {
|
||||
Method string
|
||||
Body goja.Value
|
||||
Headers map[string]string
|
||||
Timeout int // seconds
|
||||
NoCloudFlareBypass bool
|
||||
}
|
||||
|
||||
type fetchResult struct {
|
||||
body []byte
|
||||
request *req.Request
|
||||
response *req.Response
|
||||
json interface{}
|
||||
}
|
||||
|
||||
// BindFetch binds the fetch function to the VM
|
||||
func BindFetch(vm *goja.Runtime) *Fetch {
|
||||
// Create a new Fetch instance
|
||||
f := NewFetch(vm)
|
||||
_ = vm.Set("fetch", f.Fetch)
|
||||
|
||||
go func() {
|
||||
for fn := range f.ResponseChannel() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Warn().Msgf("extension: response channel panic: %v", r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
}()
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Fetch) Fetch(call goja.FunctionCall) goja.Value {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Warn().Msgf("extension: fetch panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
promise, resolve, reject := f.vm.NewPromise()
|
||||
|
||||
// Input validation
|
||||
if len(call.Arguments) == 0 {
|
||||
PanicThrowTypeError(f.vm, "fetch requires at least 1 argument")
|
||||
}
|
||||
|
||||
url, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
PanicThrowTypeError(f.vm, "URL parameter must be a string")
|
||||
}
|
||||
|
||||
// Parse options
|
||||
options := fetchOptions{
|
||||
Method: "GET",
|
||||
Timeout: int(defaultTimeout.Seconds()),
|
||||
NoCloudFlareBypass: false,
|
||||
}
|
||||
|
||||
var reqBody interface{}
|
||||
var reqContentType string
|
||||
|
||||
if len(call.Arguments) > 1 {
|
||||
rawOpts := call.Argument(1).ToObject(f.vm)
|
||||
if rawOpts != nil && !goja.IsUndefined(rawOpts) {
|
||||
|
||||
if o := rawOpts.Get("method"); o != nil && !goja.IsUndefined(o) {
|
||||
if v, ok := o.Export().(string); ok {
|
||||
options.Method = strings.ToUpper(v)
|
||||
}
|
||||
}
|
||||
if o := rawOpts.Get("timeout"); o != nil && !goja.IsUndefined(o) {
|
||||
if v, ok := o.Export().(int); ok {
|
||||
options.Timeout = v
|
||||
}
|
||||
}
|
||||
if o := rawOpts.Get("headers"); o != nil && !goja.IsUndefined(o) {
|
||||
if v, ok := o.Export().(map[string]interface{}); ok {
|
||||
for k, interf := range v {
|
||||
if str, ok := interf.(string); ok {
|
||||
if options.Headers == nil {
|
||||
options.Headers = make(map[string]string)
|
||||
}
|
||||
options.Headers[k] = str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.Body = rawOpts.Get("body")
|
||||
|
||||
if o := rawOpts.Get("noCloudflareBypass"); o != nil && !goja.IsUndefined(o) {
|
||||
if v, ok := o.Export().(bool); ok {
|
||||
options.NoCloudFlareBypass = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.Body != nil && !goja.IsUndefined(options.Body) {
|
||||
switch v := options.Body.Export().(type) {
|
||||
case string:
|
||||
reqBody = v
|
||||
case io.Reader:
|
||||
reqBody = v
|
||||
case []byte:
|
||||
reqBody = v
|
||||
case *goja.ArrayBuffer:
|
||||
reqBody = v.Bytes()
|
||||
case goja.ArrayBuffer:
|
||||
reqBody = v.Bytes()
|
||||
case *formData:
|
||||
body, mp := v.GetBuffer()
|
||||
reqBody = body
|
||||
reqContentType = mp.FormDataContentType()
|
||||
case map[string]interface{}:
|
||||
reqBody = v
|
||||
reqContentType = "application/json"
|
||||
default:
|
||||
reqBody = options.Body.String()
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Acquire semaphore
|
||||
f.fetchSem <- struct{}{}
|
||||
defer func() { <-f.fetchSem }()
|
||||
|
||||
log.Trace().Str("url", url).Str("method", options.Method).Msgf("extension: Network request")
|
||||
|
||||
var client *req.Client
|
||||
if options.NoCloudFlareBypass {
|
||||
client = clientWithoutBypass
|
||||
} else {
|
||||
client = clientWithCloudFlareBypass
|
||||
}
|
||||
|
||||
// Create request with timeout
|
||||
reqClient := client.Clone().SetTimeout(time.Duration(options.Timeout) * time.Second)
|
||||
|
||||
request := reqClient.R()
|
||||
|
||||
// Set headers
|
||||
for k, v := range options.Headers {
|
||||
request.SetHeader(k, v)
|
||||
}
|
||||
|
||||
if reqContentType != "" {
|
||||
request.SetContentType(reqContentType)
|
||||
}
|
||||
|
||||
// Set body if present
|
||||
if reqBody != nil {
|
||||
request.SetBody(reqBody)
|
||||
}
|
||||
|
||||
var result fetchResult
|
||||
var resp *req.Response
|
||||
var err error
|
||||
|
||||
switch options.Method {
|
||||
case "GET":
|
||||
resp, err = request.Get(url)
|
||||
case "POST":
|
||||
resp, err = request.Post(url)
|
||||
case "PUT":
|
||||
resp, err = request.Put(url)
|
||||
case "DELETE":
|
||||
resp, err = request.Delete(url)
|
||||
case "PATCH":
|
||||
resp, err = request.Patch(url)
|
||||
case "HEAD":
|
||||
resp, err = request.Head(url)
|
||||
case "OPTIONS":
|
||||
resp, err = request.Options(url)
|
||||
default:
|
||||
resp, err = request.Send(options.Method, url)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.vmResponseCh <- func() {
|
||||
_ = reject(NewError(f.vm, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rawBody := resp.Bytes()
|
||||
result.body = rawBody
|
||||
result.response = resp
|
||||
result.request = request
|
||||
|
||||
if len(rawBody) > 0 {
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(rawBody, &data); err != nil {
|
||||
result.json = nil
|
||||
} else {
|
||||
result.json = data
|
||||
}
|
||||
}
|
||||
|
||||
f.vmResponseCh <- func() {
|
||||
_ = resolve(result.toGojaObject(f.vm))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return f.vm.ToValue(promise)
|
||||
}
|
||||
|
||||
func (f *fetchResult) toGojaObject(vm *goja.Runtime) *goja.Object {
|
||||
obj := vm.NewObject()
|
||||
_ = obj.Set("status", f.response.StatusCode)
|
||||
_ = obj.Set("statusText", f.response.Status)
|
||||
_ = obj.Set("method", f.request.Method)
|
||||
_ = obj.Set("rawHeaders", f.response.Header)
|
||||
_ = obj.Set("ok", f.response.IsSuccessState())
|
||||
_ = obj.Set("url", f.response.Request.URL.String())
|
||||
_ = obj.Set("body", f.body)
|
||||
|
||||
headers := make(map[string]string)
|
||||
for k, v := range f.response.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
_ = obj.Set("headers", headers)
|
||||
|
||||
cookies := make(map[string]string)
|
||||
for _, cookie := range f.response.Cookies() {
|
||||
cookies[cookie.Name] = cookie.Value
|
||||
}
|
||||
_ = obj.Set("cookies", cookies)
|
||||
_ = obj.Set("redirected", f.response.Request.URL != f.response.Request.URL) // req handles redirects automatically
|
||||
_ = obj.Set("contentType", f.response.Header.Get("Content-Type"))
|
||||
_ = obj.Set("contentLength", f.response.ContentLength)
|
||||
|
||||
_ = obj.Set("text", func() string {
|
||||
return string(f.body)
|
||||
})
|
||||
|
||||
_ = obj.Set("json", func(call goja.FunctionCall) (ret goja.Value) {
|
||||
return vm.ToValue(f.json)
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
324
seanime-2.9.10/internal/goja/goja_bindings/fetch_test.go
Normal file
324
seanime-2.9.10/internal/goja/goja_bindings/fetch_test.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"seanime/internal/util"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/dop251/goja"
|
||||
gojabuffer "github.com/dop251/goja_nodejs/buffer"
|
||||
gojarequire "github.com/dop251/goja_nodejs/require"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFetch_ThreadSafety(t *testing.T) {
|
||||
// Create a test server that simulates different response times
|
||||
var serverRequestCount int
|
||||
var serverMu sync.Mutex
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
serverMu.Lock()
|
||||
serverRequestCount++
|
||||
currentRequest := serverRequestCount
|
||||
serverMu.Unlock()
|
||||
|
||||
// Simulate varying response times to increase chance of race conditions
|
||||
time.Sleep(time.Duration(currentRequest%3) * 50 * time.Millisecond)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"request": %d}`, currentRequest)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create JavaScript test code that makes concurrent fetch calls
|
||||
jsCode := fmt.Sprintf(`
|
||||
const url = %q;
|
||||
const promises = [];
|
||||
|
||||
// Function to make a fetch request and verify response
|
||||
async function makeFetch(i) {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return { index: i, data };
|
||||
}
|
||||
|
||||
// Create multiple concurrent requests
|
||||
for (let i = 0; i < 50; i++) {
|
||||
promises.push(makeFetch(i));
|
||||
}
|
||||
|
||||
// Wait for all requests to complete
|
||||
Promise.all(promises)
|
||||
`, server.URL)
|
||||
|
||||
// Run the code multiple times to increase chance of catching race conditions
|
||||
for i := 0; i < 5; i++ {
|
||||
t.Run(fmt.Sprintf("Iteration_%d", i), func(t *testing.T) {
|
||||
// Create a new VM for each iteration
|
||||
vm := goja.New()
|
||||
BindFetch(vm)
|
||||
|
||||
// Execute the JavaScript code
|
||||
v, err := vm.RunString(jsCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get the Promise
|
||||
promise, ok := v.Export().(*goja.Promise)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Wait for the Promise to resolve
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Verify the Promise resolved successfully
|
||||
assert.Equal(t, goja.PromiseStateFulfilled, promise.State())
|
||||
|
||||
// Verify we got an array of results
|
||||
results, ok := promise.Result().Export().([]interface{})
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, results, 50)
|
||||
|
||||
// Verify each result has the expected structure
|
||||
for _, result := range results {
|
||||
resultMap, ok := result.(map[string]interface{})
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, resultMap, "index")
|
||||
assert.Contains(t, resultMap, "data")
|
||||
|
||||
data, ok := resultMap["data"].(map[string]interface{})
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, data, "request")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetch_VMIsolation(t *testing.T) {
|
||||
// Create a test server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"test": "data"}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create multiple VMs and make concurrent requests
|
||||
const numVMs = 5
|
||||
const requestsPerVM = 40
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < numVMs; i++ {
|
||||
wg.Add(1)
|
||||
go func(vmIndex int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Create a new VM for this goroutine
|
||||
vm := goja.New()
|
||||
BindFetch(vm)
|
||||
|
||||
// Create JavaScript code that makes multiple requests
|
||||
jsCode := fmt.Sprintf(`
|
||||
const url = %q;
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < %d; i++) {
|
||||
promises.push(fetch(url).then(r => r.json()));
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
`, server.URL, requestsPerVM)
|
||||
|
||||
// Execute the code
|
||||
v, err := vm.RunString(jsCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get and wait for the Promise
|
||||
promise := v.Export().(*goja.Promise)
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Verify the Promise resolved successfully
|
||||
assert.Equal(t, goja.PromiseStateFulfilled, promise.State())
|
||||
|
||||
// Verify we got the expected number of results
|
||||
results := promise.Result().Export().([]interface{})
|
||||
assert.Len(t, results, requestsPerVM)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestGojaPromiseAll(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
BindFetch(vm)
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
const [a, b, c] = await Promise.all([
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/1"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/2"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/3"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/4"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/5"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/6"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/7"),
|
||||
fetch("https://jsonplaceholder.typicode.com/todos/8"),
|
||||
])
|
||||
|
||||
const dataA = await a.json();
|
||||
const dataB = await b.json();
|
||||
const dataC = await c.json();
|
||||
|
||||
console.log("Data A:", dataA.title);
|
||||
console.log("Data B:", dataB);
|
||||
console.log("Data C:", dataC);
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGojaFormDataAndFetch(t *testing.T) {
|
||||
vm := goja.New()
|
||||
BindFetch(vm)
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
const formData = new FormData();
|
||||
formData.append("username", "John");
|
||||
formData.append("accountnum", 123456);
|
||||
|
||||
console.log(formData.get("username")); // John
|
||||
|
||||
const fData = new URLSearchParams();
|
||||
for (const pair of formData.entries()) {
|
||||
fData.append(pair[0], pair[1]);
|
||||
}
|
||||
|
||||
const response = await fetch('https://httpbin.org/post', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
console.log("Echoed GojaFormData content:");
|
||||
if (data.form) {
|
||||
for (const key in data.form) {
|
||||
console.log(key, data.form[key]);
|
||||
}
|
||||
} else {
|
||||
console.log("No form data echoed in the response.");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateFulfilled {
|
||||
spew.Dump(promise.Result())
|
||||
} else {
|
||||
err := promise.Result()
|
||||
spew.Dump(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGojaFetchPostJSON(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
BindFetch(vm)
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
const response = await fetch('https://httpbin.org/post', {
|
||||
method: 'POST',
|
||||
body: { name: "John Doe", age: 30 },
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
console.log("Echoed content:");
|
||||
if (data.json) {
|
||||
for (const key in data.json) {
|
||||
console.log(key, data.json[key]);
|
||||
}
|
||||
} else {
|
||||
console.log("No form data echoed in the response.");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateFulfilled {
|
||||
spew.Dump(promise.Result())
|
||||
} else {
|
||||
err := promise.Result()
|
||||
spew.Dump(err)
|
||||
}
|
||||
}
|
||||
67
seanime-2.9.10/internal/goja/goja_bindings/fieldmapper.go
Normal file
67
seanime-2.9.10/internal/goja/goja_bindings/fieldmapper.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
var (
|
||||
_ goja.FieldNameMapper = (*DefaultFieldMapper)(nil)
|
||||
)
|
||||
|
||||
// DefaultFieldMapper provides custom mapping between Go and JavaScript methods names.
|
||||
//
|
||||
// It is similar to the builtin "uncapFieldNameMapper" but also converts
|
||||
// all uppercase identifiers to their lowercase equivalent (eg. "GET" -> "get").
|
||||
type DefaultFieldMapper struct {
|
||||
}
|
||||
|
||||
// FieldName implements the [FieldNameMapper.FieldName] interface method.
|
||||
func (u DefaultFieldMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// MethodName implements the [FieldNameMapper.MethodName] interface method.
|
||||
func (u DefaultFieldMapper) MethodName(_ reflect.Type, m reflect.Method) string {
|
||||
return convertGoToJSName(m.Name)
|
||||
}
|
||||
|
||||
var nameExceptions = map[string]string{"OAuth2": "oauth2"}
|
||||
|
||||
func convertGoToJSName(name string) string {
|
||||
if v, ok := nameExceptions[name]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
startUppercase := make([]rune, 0, len(name))
|
||||
|
||||
for _, c := range name {
|
||||
if c != '_' && !unicode.IsUpper(c) && !unicode.IsDigit(c) {
|
||||
break
|
||||
}
|
||||
|
||||
startUppercase = append(startUppercase, c)
|
||||
}
|
||||
|
||||
totalStartUppercase := len(startUppercase)
|
||||
|
||||
// all uppercase eg. "JSON" -> "json"
|
||||
if len(name) == totalStartUppercase {
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
||||
// eg. "JSONField" -> "jsonField"
|
||||
if totalStartUppercase > 1 {
|
||||
return strings.ToLower(name[0:totalStartUppercase-1]) + name[totalStartUppercase-1:]
|
||||
}
|
||||
|
||||
// eg. "GetField" -> "getField"
|
||||
if totalStartUppercase == 1 {
|
||||
return strings.ToLower(name[0:1]) + name[1:]
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
231
seanime-2.9.10/internal/goja/goja_bindings/formdata.go
Normal file
231
seanime-2.9.10/internal/goja/goja_bindings/formdata.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// formData
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func BindFormData(vm *goja.Runtime) error {
|
||||
err := vm.Set("FormData", func(call goja.ConstructorCall) *goja.Object {
|
||||
fd := newFormData(vm)
|
||||
|
||||
instanceValue := vm.ToValue(fd).(*goja.Object)
|
||||
instanceValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return instanceValue
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type formData struct {
|
||||
runtime *goja.Runtime
|
||||
buf *bytes.Buffer
|
||||
writer *multipart.Writer
|
||||
fieldNames map[string]struct{}
|
||||
values map[string][]string
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newFormData(runtime *goja.Runtime) *formData {
|
||||
buf := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(buf)
|
||||
return &formData{
|
||||
runtime: runtime,
|
||||
buf: buf,
|
||||
writer: writer,
|
||||
fieldNames: make(map[string]struct{}),
|
||||
values: make(map[string][]string),
|
||||
closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *formData) Append(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot append to closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
value := call.Argument(1).String()
|
||||
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
fd.values[fieldName] = append(fd.values[fieldName], value)
|
||||
|
||||
if _, exists := fd.fieldNames[fieldName]; !exists {
|
||||
fd.fieldNames[fieldName] = struct{}{}
|
||||
writer, err := fd.writer.CreateFormField(fieldName)
|
||||
if err != nil {
|
||||
return fd.runtime.ToValue(err.Error())
|
||||
}
|
||||
_, err = writer.Write([]byte(value))
|
||||
if err != nil {
|
||||
return fd.runtime.ToValue(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
func (fd *formData) Delete(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot delete from closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
|
||||
delete(fd.fieldNames, fieldName)
|
||||
delete(fd.values, fieldName)
|
||||
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
func (fd *formData) Entries(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot get entries from closed FormData")
|
||||
}
|
||||
|
||||
iter := fd.runtime.NewArray()
|
||||
index := 0
|
||||
for key, values := range fd.values {
|
||||
for _, value := range values {
|
||||
entry := fd.runtime.NewObject()
|
||||
entry.Set("0", key)
|
||||
entry.Set("1", value)
|
||||
iter.Set(strconv.Itoa(index), entry)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func (fd *formData) Get(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot get value from closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
|
||||
if values, exists := fd.values[fieldName]; exists && len(values) > 0 {
|
||||
return fd.runtime.ToValue(values[0])
|
||||
}
|
||||
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
func (fd *formData) GetAll(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot get all values from closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
|
||||
iter := fd.runtime.NewArray()
|
||||
if values, exists := fd.values[fieldName]; exists {
|
||||
for i, value := range values {
|
||||
iter.Set(strconv.Itoa(i), value)
|
||||
}
|
||||
}
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func (fd *formData) Has(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot check key in closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
|
||||
_, exists := fd.fieldNames[fieldName]
|
||||
return fd.runtime.ToValue(exists)
|
||||
}
|
||||
|
||||
func (fd *formData) Keys(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot get keys from closed FormData")
|
||||
}
|
||||
|
||||
iter := fd.runtime.NewArray()
|
||||
index := 0
|
||||
for key := range fd.fieldNames {
|
||||
iter.Set(strconv.Itoa(index), key)
|
||||
index++
|
||||
}
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func (fd *formData) Set(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot set value in closed FormData")
|
||||
}
|
||||
|
||||
fieldName := call.Argument(0).String()
|
||||
value := call.Argument(1).String()
|
||||
|
||||
fieldName = strings.TrimSpace(fieldName)
|
||||
fd.values[fieldName] = []string{value}
|
||||
|
||||
if _, exists := fd.fieldNames[fieldName]; !exists {
|
||||
fd.fieldNames[fieldName] = struct{}{}
|
||||
writer, err := fd.writer.CreateFormField(fieldName)
|
||||
if err != nil {
|
||||
return fd.runtime.ToValue(err.Error())
|
||||
}
|
||||
_, err = writer.Write([]byte(value))
|
||||
if err != nil {
|
||||
return fd.runtime.ToValue(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
func (fd *formData) Values(call goja.FunctionCall) goja.Value {
|
||||
if fd.closed {
|
||||
return fd.runtime.ToValue("cannot get values from closed FormData")
|
||||
}
|
||||
|
||||
iter := fd.runtime.NewArray()
|
||||
index := 0
|
||||
for _, values := range fd.values {
|
||||
for _, value := range values {
|
||||
iter.Set(strconv.Itoa(index), value)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func (fd *formData) GetContentType() goja.Value {
|
||||
if !fd.closed {
|
||||
fd.writer.Close()
|
||||
fd.closed = true
|
||||
}
|
||||
return fd.runtime.ToValue(fd.writer.FormDataContentType())
|
||||
}
|
||||
|
||||
func (fd *formData) GetBuffer() (io.Reader, *multipart.Writer) {
|
||||
if !fd.closed {
|
||||
fd.writer.Close()
|
||||
fd.closed = true
|
||||
}
|
||||
return bytes.NewReader(fd.buf.Bytes()), fd.writer
|
||||
}
|
||||
49
seanime-2.9.10/internal/goja/goja_bindings/formdata_test.go
Normal file
49
seanime-2.9.10/internal/goja/goja_bindings/formdata_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
gojabuffer "github.com/dop251/goja_nodejs/buffer"
|
||||
gojarequire "github.com/dop251/goja_nodejs/require"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGojaFormData(t *testing.T) {
|
||||
vm := goja.New()
|
||||
defer vm.ClearInterrupt()
|
||||
|
||||
BindFormData(vm)
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
|
||||
_, err := vm.RunString(`
|
||||
var fd = new FormData();
|
||||
fd.append("name", "John Doe");
|
||||
fd.append("age", 30);
|
||||
|
||||
console.log("Has 'name':", fd.has("name")); // true
|
||||
console.log("Get 'name':", fd.get("name")); // John Doe
|
||||
console.log("GetAll 'name':", fd.getAll("name")); // ["John Doe"]
|
||||
console.log("Keys:", Array.from(fd.keys())); // ["name", "age"]
|
||||
console.log("Values:", Array.from(fd.values())); // ["John Doe", 30]
|
||||
|
||||
fd.delete("name");
|
||||
console.log("Has 'name' after delete:", fd.has("name")); // false
|
||||
|
||||
console.log("Entries:");
|
||||
for (let entry of fd.entries()) {
|
||||
console.log(entry[0], entry[1]);
|
||||
}
|
||||
|
||||
var contentType = fd.getContentType();
|
||||
var buffer = fd.getBuffer();
|
||||
console.log("Content-Type:", contentType);
|
||||
console.log("Buffer:", buffer);
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/// <reference path="../doc.d.ts" />
|
||||
|
||||
class Provider {
|
||||
async test() {
|
||||
try {
|
||||
const data = await fetch("https://cryptojs.gitbook.io/docs")
|
||||
|
||||
const $ = LoadDoc(await data.text())
|
||||
|
||||
console.log($("header h1").text())
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/// <reference path="../doc.d.ts" />
|
||||
|
||||
class Provider {
|
||||
async test() {
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Document</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Main Title</h1>
|
||||
<nav>
|
||||
<ul id="main-nav" class="nav-list">
|
||||
<li><a href="#home">Home</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<section id="content">
|
||||
<article class="post" data-id="1">
|
||||
<h2>First Post</h2>
|
||||
<p>This is the first post.</p>
|
||||
<a href="https://example.com/first-post" class="read-more">Read more</a>
|
||||
</article>
|
||||
<article class="post" data-id="2">
|
||||
<h2>Second Post</h2>
|
||||
<p>This is the second post.</p>
|
||||
<a href="https://example.com/second-post" class="read-more">Read more</a>
|
||||
</article>
|
||||
<article class="post" data-id="3">
|
||||
<h2>Third Post</h2>
|
||||
<p>This is the third post.</p>
|
||||
<a href="https://example.com/third-post" class="read-more">Read more</a>
|
||||
</article>
|
||||
</section>
|
||||
<aside>
|
||||
<h2>Sidebar</h2>
|
||||
<ul class="sidebar-list">
|
||||
<li><a href="#link1">Link 1</a></li>
|
||||
<li><a href="#link2">Link 2</a></li>
|
||||
<li><a href="#link3">Link 3</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
<footer>
|
||||
<p>© 2024 Example Company. All rights reserved.</p>
|
||||
<p><a href="mailto:info@example.com">Contact Us</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
const $ = new Doc(html)
|
||||
|
||||
console.log("Document created")
|
||||
|
||||
console.log(">>> Last post by string selector")
|
||||
const article = $.find("article:last-child")
|
||||
console.log(article.html())
|
||||
|
||||
console.log(">>> Post titles (map to string)")
|
||||
|
||||
const titles = $.find("section")
|
||||
.children("article.post")
|
||||
.filter((i, e) => {
|
||||
return e.attr("data-id") !== "1"
|
||||
})
|
||||
.map((i, e) => {
|
||||
return e.children("h2").text()
|
||||
})
|
||||
|
||||
console.log(titles)
|
||||
|
||||
console.log(">>> END")
|
||||
|
||||
}
|
||||
}
|
||||
46
seanime-2.9.10/internal/goja/goja_bindings/torrent.go
Normal file
46
seanime-2.9.10/internal/goja/goja_bindings/torrent.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"seanime/internal/torrents/torrent"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func BindTorrentUtils(vm *goja.Runtime) error {
|
||||
torrentUtils := vm.NewObject()
|
||||
torrentUtils.Set("getMagnetLinkFromTorrentData", getMagnetLinkFromTorrentDataFunc(vm))
|
||||
vm.Set("$torrentUtils", torrentUtils)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMagnetLinkFromTorrentDataFunc(vm *goja.Runtime) (ret func(c goja.FunctionCall) goja.Value) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
}
|
||||
}()
|
||||
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panic(vm.ToValue("selection is nil"))
|
||||
}
|
||||
}()
|
||||
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(vm.ToValue("TypeError: getMagnetLinkFromTorrentData requires at least 1 argument"))
|
||||
}
|
||||
|
||||
str, ok := call.Argument(0).Export().(string)
|
||||
if !ok {
|
||||
panic(vm.ToValue(vm.NewTypeError("argument is not a string")))
|
||||
}
|
||||
|
||||
magnet, err := torrent.StrDataToMagnetLink(str)
|
||||
if err != nil {
|
||||
return vm.ToValue("")
|
||||
}
|
||||
|
||||
return vm.ToValue(magnet)
|
||||
}
|
||||
}
|
||||
61
seanime-2.9.10/internal/goja/goja_bindings/torrent_test.go
Normal file
61
seanime-2.9.10/internal/goja/goja_bindings/torrent_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
gojabuffer "github.com/dop251/goja_nodejs/buffer"
|
||||
gojarequire "github.com/dop251/goja_nodejs/require"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGojaTorrentUtils(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
gojabuffer.Enable(vm)
|
||||
BindTorrentUtils(vm)
|
||||
BindConsole(vm, util.NewLogger())
|
||||
BindFetch(vm)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
async function run() {
|
||||
try {
|
||||
|
||||
console.log("\nTesting torrent file to magnet link")
|
||||
|
||||
const url = "https://animetosho.org/storage/torrent/da9aad67b6f8bb82757bb3ef95235b42624c34f7/%5BSubsPlease%5D%20Make%20Heroine%20ga%20Oosugiru%21%20-%2011%20%281080p%29%20%5B58B3496A%5D.torrent"
|
||||
|
||||
const data = await (await fetch(url)).text()
|
||||
|
||||
const magnetLink = getMagnetLinkFromTorrentData(data)
|
||||
|
||||
console.log("Magnet link:", magnetLink)
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
runFunc, ok := goja.AssertFunction(vm.Get("run"))
|
||||
require.True(t, ok)
|
||||
|
||||
ret, err := runFunc(goja.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
promise := ret.Export().(*goja.Promise)
|
||||
|
||||
for promise.State() == goja.PromiseStatePending {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if promise.State() == goja.PromiseStateRejected {
|
||||
err := promise.Result()
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package goja_runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"seanime/internal/util/result"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Manager manages a shared pool of Goja runtimes for all extensions.
|
||||
type Manager struct {
|
||||
pluginPools *result.Map[string, *Pool]
|
||||
basePool *Pool
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
sp sync.Pool
|
||||
factory func() *goja.Runtime
|
||||
logger *zerolog.Logger
|
||||
size int32
|
||||
metrics metrics
|
||||
}
|
||||
|
||||
// metrics holds counters for pool stats.
|
||||
type metrics struct {
|
||||
prewarmed atomic.Int64
|
||||
created atomic.Int64
|
||||
reused atomic.Int64
|
||||
timeouts atomic.Int64
|
||||
invocations atomic.Int64
|
||||
}
|
||||
|
||||
func NewManager(logger *zerolog.Logger) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreatePrivatePool returns the pool for the given extension.
|
||||
func (m *Manager) GetOrCreatePrivatePool(extID string, initFn func() *goja.Runtime) (*Pool, error) {
|
||||
if m.pluginPools == nil {
|
||||
m.pluginPools = result.NewResultMap[string, *Pool]()
|
||||
}
|
||||
|
||||
pool, ok := m.pluginPools.Get(extID)
|
||||
if !ok {
|
||||
pool = newPool(5, initFn, m.logger)
|
||||
m.pluginPools.Set(extID, pool)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (m *Manager) DeletePluginPool(extID string) {
|
||||
m.logger.Trace().Msgf("plugin: Deleting pool for extension %s", extID)
|
||||
if m.pluginPools == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the pool first to interrupt all runtimes
|
||||
if pool, ok := m.pluginPools.Get(extID); ok {
|
||||
// Drain the pool and interrupt all runtimes
|
||||
m.logger.Debug().Msgf("plugin: Interrupting all runtimes in pool for extension %s", extID)
|
||||
|
||||
interruptedCount := 0
|
||||
for {
|
||||
// Get a runtime without using a context to avoid blocking
|
||||
runtimeV := pool.sp.Get()
|
||||
if runtimeV == nil {
|
||||
break // No more runtimes in the pool or error occurred
|
||||
}
|
||||
|
||||
runtime, ok := runtimeV.(*goja.Runtime)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// Interrupt the runtime
|
||||
runtime.ClearInterrupt()
|
||||
interruptedCount++
|
||||
}
|
||||
|
||||
m.logger.Debug().Msgf("plugin: Interrupted %d runtimes in pool for extension %s", interruptedCount, extID)
|
||||
}
|
||||
|
||||
// Delete the pool
|
||||
m.pluginPools.Delete(extID)
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// GetOrCreateSharedPool returns the shared pool.
|
||||
func (m *Manager) GetOrCreateSharedPool(initFn func() *goja.Runtime) (*Pool, error) {
|
||||
if m.basePool == nil {
|
||||
m.basePool = newPool(15, initFn, m.logger)
|
||||
}
|
||||
return m.basePool, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Run(ctx context.Context, extID string, fn func(*goja.Runtime) error) error {
|
||||
pool, ok := m.pluginPools.Get(extID)
|
||||
if !ok {
|
||||
return fmt.Errorf("plugin pool not found for extension ID: %s", extID)
|
||||
}
|
||||
runtime, err := pool.Get(ctx)
|
||||
pool.metrics.invocations.Add(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pool.Put(runtime)
|
||||
return fn(runtime)
|
||||
}
|
||||
|
||||
func (m *Manager) RunShared(ctx context.Context, fn func(*goja.Runtime) error) error {
|
||||
runtime, err := m.basePool.Get(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.basePool.Put(runtime)
|
||||
return fn(runtime)
|
||||
}
|
||||
|
||||
func (m *Manager) GetLogger() *zerolog.Logger {
|
||||
return m.logger
|
||||
}
|
||||
|
||||
func (m *Manager) PrintPluginPoolMetrics(extID string) {
|
||||
if m.pluginPools == nil {
|
||||
return
|
||||
}
|
||||
pool, ok := m.pluginPools.Get(extID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
stats := pool.Stats()
|
||||
m.logger.Trace().
|
||||
Int64("prewarmed", stats["prewarmed"]).
|
||||
Int64("created", stats["created"]).
|
||||
Int64("reused", stats["reused"]).
|
||||
Int64("timeouts", stats["timeouts"]).
|
||||
Int64("invocations", stats["invocations"]).
|
||||
Msg("goja runtime: VM Pool Metrics")
|
||||
}
|
||||
|
||||
func (m *Manager) PrintBasePoolMetrics() {
|
||||
if m.basePool == nil {
|
||||
return
|
||||
}
|
||||
stats := m.basePool.Stats()
|
||||
m.logger.Trace().
|
||||
Int64("prewarmed", stats["prewarmed"]).
|
||||
Int64("created", stats["created"]).
|
||||
Int64("reused", stats["reused"]).
|
||||
Int64("invocations", stats["invocations"]).
|
||||
Int64("timeouts", stats["timeouts"]).
|
||||
Msg("goja runtime: Base VM Pool Metrics")
|
||||
}
|
||||
|
||||
// newPool creates a new Pool using sync.Pool, pre-warming it with size items.
|
||||
func newPool(size int32, initFn func() *goja.Runtime, logger *zerolog.Logger) *Pool {
|
||||
p := &Pool{
|
||||
factory: initFn,
|
||||
logger: logger,
|
||||
size: size,
|
||||
}
|
||||
|
||||
// p.sp.New = func() interface{} {
|
||||
// runtime := initFn()
|
||||
// p.metrics.created.Add(1)
|
||||
// return runtime
|
||||
// }
|
||||
|
||||
p.sp.New = func() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pre-warm the pool
|
||||
logger.Trace().Int32("size", size).Msg("goja runtime: Pre-warming pool")
|
||||
for i := int32(0); i < size; i++ {
|
||||
r := initFn()
|
||||
p.sp.Put(r)
|
||||
p.metrics.prewarmed.Add(1)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Get retrieves a runtime from the pool or creates a new one. It respects the context for cancellation.
|
||||
func (p *Pool) Get(ctx context.Context) (*goja.Runtime, error) {
|
||||
v := p.sp.Get()
|
||||
if v == nil {
|
||||
// If sync.Pool.New returned nil or context canceled, try factory manually.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
p.metrics.timeouts.Add(1)
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
runtime := p.factory()
|
||||
p.metrics.created.Add(1)
|
||||
return runtime, nil
|
||||
}
|
||||
p.metrics.reused.Add(1)
|
||||
return v.(*goja.Runtime), nil
|
||||
}
|
||||
|
||||
// Put returns a runtime to the pool after clearing its interrupt.
|
||||
func (p *Pool) Put(runtime *goja.Runtime) {
|
||||
if runtime == nil {
|
||||
return
|
||||
}
|
||||
runtime.ClearInterrupt()
|
||||
p.sp.Put(runtime)
|
||||
}
|
||||
|
||||
// Stats returns pool metrics as a map.
|
||||
func (p *Pool) Stats() map[string]int64 {
|
||||
return map[string]int64{
|
||||
"prewarmed": p.metrics.prewarmed.Load(),
|
||||
"invocations": p.metrics.invocations.Load(),
|
||||
"created": p.metrics.created.Load(),
|
||||
"reused": p.metrics.reused.Load(),
|
||||
"timeouts": p.metrics.timeouts.Load(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user