node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

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