update throw exception
add util.load and util.save
This commit is contained in:
parent
5faf209709
commit
767d87ac3e
23
go.mod
23
go.mod
@ -3,30 +3,31 @@ module apigo.cc/ai/ai
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/dop251/goja v0.0.0-20240828124009-016eb7256539
|
||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc
|
||||
github.com/Masterminds/semver/v3 v3.3.0
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-resty/resty/v2 v2.15.0
|
||||
github.com/go-resty/resty/v2 v2.15.2
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/sashabaranov/go-openai v1.29.2
|
||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134
|
||||
github.com/sashabaranov/go-openai v1.30.3
|
||||
github.com/ssgo/config v1.7.7
|
||||
github.com/ssgo/httpclient v1.7.7
|
||||
github.com/ssgo/log v1.7.7
|
||||
github.com/ssgo/u v1.7.7
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/text v0.18.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/ssgo/standard v1.7.7 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/dop251/goja v0.0.0-20240828124009-016eb7256539 => ./goja
|
||||
|
@ -3,7 +3,7 @@ goja
|
||||
|
||||
ECMAScript 5.1(+) implementation in Go.
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/dop251/goja.svg)](https://pkg.go.dev/github.com/dop251/goja)
|
||||
[![Go Reference](https://pkg.go.dev/badge/apigo.cc/ai/ai/goja.svg)](https://pkg.go.dev/apigo.cc/ai/ai/goja)
|
||||
|
||||
Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and
|
||||
performance.
|
||||
@ -20,7 +20,7 @@ Features
|
||||
pass all of them. See .tc39_test262_checkout.sh for the latest working commit id.
|
||||
* Capable of running Babel, Typescript compiler and pretty much anything written in ES5.
|
||||
* Sourcemaps.
|
||||
* Most of ES6 functionality, still work in progress, see https://github.com/dop251/goja/milestone/1?closed=1
|
||||
* Most of ES6 functionality, still work in progress, see https://apigo.cc/ai/ai/goja/milestone/1?closed=1
|
||||
|
||||
Known incompatibilities and caveats
|
||||
-----------------------------------
|
||||
@ -48,7 +48,7 @@ key = undefined; // Now it does
|
||||
The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer
|
||||
set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution
|
||||
above is the only reasonable way I can think of without involving finalizers. This is the third attempt
|
||||
(see https://github.com/dop251/goja/issues/250 and https://github.com/dop251/goja/issues/199 for more details).
|
||||
(see https://apigo.cc/ai/ai/goja/issues/250 and https://apigo.cc/ai/ai/goja/issues/199 for more details).
|
||||
|
||||
Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.
|
||||
|
||||
@ -80,7 +80,7 @@ FAQ
|
||||
Although it's faster than many scripting language implementations in Go I have seen
|
||||
(for example it's 6-7 times faster than otto on average) it is not a
|
||||
replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine.
|
||||
You can find some benchmarks [here](https://github.com/dop251/goja/issues/2).
|
||||
You can find some benchmarks [here](https://apigo.cc/ai/ai/goja/issues/2).
|
||||
|
||||
### Why would I want to use it over a V8 wrapper?
|
||||
|
||||
@ -106,13 +106,13 @@ it's not possible to pass object values between runtimes.
|
||||
|
||||
setTimeout() assumes concurrent execution of code which requires an execution
|
||||
environment, for example an event loop similar to nodejs or a browser.
|
||||
There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some NodeJS functionality,
|
||||
There is a [separate project](https://apigo.cc/ai/ai/goja_nodejs) aimed at providing some NodeJS functionality,
|
||||
and it includes an event loop.
|
||||
|
||||
### Can you implement (feature X from ES6 or higher)?
|
||||
|
||||
I will be adding features in their dependency order and as quickly as time permits. Please do not ask
|
||||
for ETAs. Features that are open in the [milestone](https://github.com/dop251/goja/milestone/1) are either in progress
|
||||
for ETAs. Features that are open in the [milestone](https://apigo.cc/ai/ai/goja/milestone/1) are either in progress
|
||||
or will be worked on next.
|
||||
|
||||
The ongoing work is done in separate feature branches which are merged into master when appropriate.
|
||||
@ -153,13 +153,13 @@ if num := v.Export().(int64); num != 4 {
|
||||
|
||||
Passing Values to JS
|
||||
--------------------
|
||||
Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) for more details.
|
||||
Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/apigo.cc/ai/ai/goja#Runtime.ToValue) for more details.
|
||||
|
||||
Exporting Values from JS
|
||||
------------------------
|
||||
A JS value can be exported into its default Go representation using Value.Export() method.
|
||||
|
||||
Alternatively it can be exported into a specific Go variable using [Runtime.ExportTo()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo) method.
|
||||
Alternatively it can be exported into a specific Go variable using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/ai/ai/goja#Runtime.ExportTo) method.
|
||||
|
||||
Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or
|
||||
a pointer to the same struct). This includes circular objects and makes it possible to export them.
|
||||
@ -168,7 +168,7 @@ Calling JS functions from Go
|
||||
----------------------------
|
||||
There are 2 approaches:
|
||||
|
||||
- Using [AssertFunction()](https://pkg.go.dev/github.com/dop251/goja#AssertFunction):
|
||||
- Using [AssertFunction()](https://pkg.go.dev/apigo.cc/ai/ai/goja#AssertFunction):
|
||||
```go
|
||||
const SCRIPT = `
|
||||
function sum(a, b) {
|
||||
@ -193,7 +193,7 @@ if err != nil {
|
||||
fmt.Println(res)
|
||||
// Output: 42
|
||||
```
|
||||
- Using [Runtime.ExportTo()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo):
|
||||
- Using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/ai/ai/goja#Runtime.ExportTo):
|
||||
```go
|
||||
const SCRIPT = `
|
||||
function sum(a, b) {
|
||||
@ -224,7 +224,7 @@ Mapping struct field and method names
|
||||
-------------------------------------
|
||||
By default, the names are passed through as is which means they are capitalised. This does not match
|
||||
the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are
|
||||
dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#FieldNameMapper):
|
||||
dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/apigo.cc/ai/ai/goja#FieldNameMapper):
|
||||
|
||||
```go
|
||||
vm := goja.New()
|
||||
@ -238,14 +238,14 @@ fmt.Println(res.Export())
|
||||
// Output: 42
|
||||
```
|
||||
|
||||
There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#TagFieldNameMapper) and
|
||||
[UncapFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#UncapFieldNameMapper), or you can use your own implementation.
|
||||
There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/apigo.cc/ai/ai/goja#TagFieldNameMapper) and
|
||||
[UncapFieldNameMapper](https://pkg.go.dev/apigo.cc/ai/ai/goja#UncapFieldNameMapper), or you can use your own implementation.
|
||||
|
||||
Native Constructors
|
||||
-------------------
|
||||
|
||||
In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`.
|
||||
See [Runtime.ToValue()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) documentation for more details.
|
||||
See [Runtime.ToValue()](https://pkg.go.dev/apigo.cc/ai/ai/goja#Runtime.ToValue) documentation for more details.
|
||||
|
||||
Regular Expressions
|
||||
-------------------
|
||||
@ -331,4 +331,4 @@ func TestInterrupt(t *testing.T) {
|
||||
NodeJS Compatibility
|
||||
--------------------
|
||||
|
||||
There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some of the NodeJS functionality.
|
||||
There is a [separate project](https://apigo.cc/ai/ai/goja_nodejs) aimed at providing some of the NodeJS functionality.
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type arrayIterObject struct {
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type sparseArrayItem struct {
|
||||
|
@ -1,6 +1,6 @@
|
||||
# ast
|
||||
--
|
||||
import "github.com/dop251/goja/ast"
|
||||
import "apigo.cc/ai/ai/goja/ast"
|
||||
|
||||
Package ast declares types representing a JavaScript AST.
|
||||
|
||||
|
@ -9,9 +9,9 @@ node types are concerned) and may change in the future.
|
||||
package ast
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type PropertyKind string
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type valueBigInt big.Int
|
||||
|
@ -1,6 +1,6 @@
|
||||
package goja
|
||||
|
||||
import "github.com/dop251/goja/unistring"
|
||||
import "apigo.cc/ai/ai/goja/unistring"
|
||||
|
||||
const propNameStack = "stack"
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const hexUpper = "0123456789ABCDEF"
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const hex = "0123456789abcdef"
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja/ftoa"
|
||||
"apigo.cc/ai/ai/goja/ftoa"
|
||||
)
|
||||
|
||||
func (r *Runtime) toNumber(v Value) Value {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -605,7 +605,7 @@ func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
|
||||
// NewPromise creates and returns a Promise and resolving functions for it.
|
||||
//
|
||||
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
|
||||
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://github.com/dop251/goja_nodejs)
|
||||
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://apigo.cc/ai/ai/goja_nodejs)
|
||||
// where it can be used like this:
|
||||
//
|
||||
// loop := NewEventLoop()
|
||||
|
@ -1,7 +1,7 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type nativeProxyHandler struct {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"fmt"
|
||||
"github.com/dop251/goja/parser"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
@ -1,6 +1,6 @@
|
||||
package goja
|
||||
|
||||
import "github.com/dop251/goja/unistring"
|
||||
import "apigo.cc/ai/ai/goja/unistring"
|
||||
|
||||
var (
|
||||
SymHasInstance = newSymbol(asciiString("Symbol.hasInstance"))
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type typedArraySortCtx struct {
|
||||
|
@ -1,13 +1,13 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"fmt"
|
||||
"github.com/dop251/goja/token"
|
||||
"sort"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type blockType int
|
||||
|
@ -3,10 +3,10 @@ package goja
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type compiledExpr interface {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const TESTLIB = `
|
||||
|
@ -1,7 +1,7 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# file
|
||||
--
|
||||
import "github.com/dop251/goja/file"
|
||||
import "apigo.cc/ai/ai/goja/file"
|
||||
|
||||
Package file encapsulates the file abstractions used by the ast & parser.
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/ftoa/internal/fast"
|
||||
"apigo.cc/ai/ai/goja/ftoa/internal/fast"
|
||||
)
|
||||
|
||||
type FToStrMode int
|
||||
|
56
goja/func.go
56
goja/func.go
@ -1,11 +1,9 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type resultType uint8
|
||||
@ -570,34 +568,34 @@ func (f *nativeFuncObject) vmCall(vm *vm, n int) {
|
||||
ret = _undefined
|
||||
}
|
||||
//fmt.Println(">>>>>>>>>>>>>>>>>>>>>>", ret.toString().String())
|
||||
if strings.HasPrefix(ret.toString().String(), "GoError: ") {
|
||||
//fmt.Println(">>>>>>>>>>>>>>>>>>>>>>", 111)
|
||||
//vm.throw(Exception{
|
||||
// val: ret.toString(),
|
||||
//})
|
||||
//stack := make([]StackFrame, 0)
|
||||
//for i := len(vm.callStack) - 1; i >= 0; i-- {
|
||||
// frame := &vm.callStack[i]
|
||||
// if frame.prg != nil || frame.sb > 0 {
|
||||
// var funcName unistring.String
|
||||
// if prg := frame.prg; prg != nil {
|
||||
// funcName = prg.funcName
|
||||
// } else {
|
||||
// funcName = getFuncName(vm.stack, frame.sb)
|
||||
// }
|
||||
// stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: frame.pc, funcName: funcName})
|
||||
// //prg := vm.callStack[i].prg
|
||||
// //stack = append(stack, string(funcName)+" "+prg.src.Position(prg.sourceOffset(frame.pc)).String())
|
||||
// }
|
||||
//}
|
||||
vm.throw(&Exception{
|
||||
val: ret.ToString(),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
//if strings.HasPrefix(ret.toString().String(), "GoError: ") {
|
||||
// //fmt.Println(">>>>>>>>>>>>>>>>>>>>>>", 111)
|
||||
// //vm.throw(Exception{
|
||||
// // val: ret.toString(),
|
||||
// //})
|
||||
// //stack := make([]StackFrame, 0)
|
||||
// //for i := len(vm.callStack) - 1; i >= 0; i-- {
|
||||
// // frame := &vm.callStack[i]
|
||||
// // if frame.prg != nil || frame.sb > 0 {
|
||||
// // var funcName unistring.String
|
||||
// // if prg := frame.prg; prg != nil {
|
||||
// // funcName = prg.funcName
|
||||
// // } else {
|
||||
// // funcName = getFuncName(vm.stack, frame.sb)
|
||||
// // }
|
||||
// // stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: frame.pc, funcName: funcName})
|
||||
// // //prg := vm.callStack[i].prg
|
||||
// // //stack = append(stack, string(funcName)+" "+prg.src.Position(prg.sourceOffset(frame.pc)).String())
|
||||
// // }
|
||||
// //}
|
||||
// vm.throw(&Exception{
|
||||
// val: ret.ToString(),
|
||||
// })
|
||||
// return
|
||||
//} else {
|
||||
vm.stack[vm.sp-n-2] = ret
|
||||
vm.popCtx()
|
||||
}
|
||||
//}
|
||||
} else {
|
||||
vm.stack[vm.sp-n-2] = _undefined
|
||||
}
|
||||
|
15
goja/go.mod
15
goja/go.mod
@ -1,15 +0,0 @@
|
||||
module github.com/dop251/goja
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904
|
||||
golang.org/x/text v0.3.8
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require github.com/kr/pretty v0.3.0 // indirect
|
@ -13,9 +13,9 @@ import (
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/console"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -1,6 +1,6 @@
|
||||
package goja
|
||||
|
||||
import "github.com/dop251/goja/unistring"
|
||||
import "apigo.cc/ai/ai/goja/unistring"
|
||||
|
||||
type argumentsObject struct {
|
||||
baseObject
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type objectGoArrayReflect struct {
|
||||
|
@ -3,7 +3,7 @@ package goja
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type objectGoMapSimple struct {
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type objectGoMapReflect struct {
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
// JsonEncodable allows custom JSON encoding by JSON.stringify()
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type objectGoSlice struct {
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"math/bits"
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type objectGoSliceReflect struct {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"fmt"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
@ -1,11 +1,11 @@
|
||||
# parser
|
||||
--
|
||||
import "github.com/dop251/goja/parser"
|
||||
import "apigo.cc/ai/ai/goja/parser"
|
||||
|
||||
Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -3,10 +3,10 @@ package parser
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
func (self *_parser) parseIdentifier() *ast.Identifier {
|
||||
|
@ -12,9 +12,9 @@ import (
|
||||
|
||||
"golang.org/x/text/unicode/rangetable"
|
||||
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -3,9 +3,9 @@ package parser
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
)
|
||||
|
||||
func marshal(name string, children ...interface{}) interface{} {
|
||||
|
@ -2,7 +2,7 @@
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
@ -38,10 +38,10 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
// A Mode value is a set of flags (or 0). They control optional parser functionality.
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
func firstErr(err error) error {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type _scope struct {
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/token"
|
||||
"github.com/go-sourcemap/sourcemap"
|
||||
)
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
// Proxy is a Go wrapper around ECMAScript Proxy. Calling Runtime.ToValue() on it
|
||||
|
@ -1,9 +1,9 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"fmt"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
@ -17,10 +17,10 @@ import (
|
||||
|
||||
"golang.org/x/text/collate"
|
||||
|
||||
js_ast "github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
js_ast "apigo.cc/ai/ai/goja/ast"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
)
|
||||
|
||||
func TestGlobalObjectProto(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type asciiString string
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# token
|
||||
--
|
||||
import "github.com/dop251/goja/token"
|
||||
import "apigo.cc/ai/ai/goja/token"
|
||||
|
||||
Package token defines constants representing the lexical tokens of JavaScript
|
||||
(ECMA5).
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
type byteOrder bool
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dop251/goja/ftoa"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/ftoa"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -1,9 +1,9 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"apigo.cc/ai/ai/goja/file"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
"apigo.cc/ai/ai/goja/unistring"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
13
goja_nodejs/LICENSE
Normal file
13
goja_nodejs/LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright (c) 2016 Dmitry Panov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
goja_nodejs/README.md
Normal file
32
goja_nodejs/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
Nodejs compatibility library for Goja
|
||||
====
|
||||
|
||||
This is a collection of Goja modules that provide nodejs compatibility.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := new(require.Registry) // this can be shared by multiple runtimes
|
||||
|
||||
runtime := goja.New()
|
||||
req := registry.Enable(runtime)
|
||||
|
||||
runtime.RunString(`
|
||||
var m = require("./m.js");
|
||||
m.test();
|
||||
`)
|
||||
|
||||
m, err := req.Require("./m.js")
|
||||
_, _ = m, err
|
||||
}
|
||||
```
|
||||
|
||||
More modules will be added. Contributions welcome too.
|
83
goja_nodejs/assert.js
Normal file
83
goja_nodejs/assert.js
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const assert = {
|
||||
_isSameValue(a, b) {
|
||||
if (a === b) {
|
||||
// Handle +/-0 vs. -/+0
|
||||
return a !== 0 || 1 / a === 1 / b;
|
||||
}
|
||||
|
||||
// Handle NaN vs. NaN
|
||||
return a !== a && b !== b;
|
||||
},
|
||||
|
||||
_toString(value) {
|
||||
try {
|
||||
if (value === 0 && 1 / value === -Infinity) {
|
||||
return '-0';
|
||||
}
|
||||
|
||||
return String(value);
|
||||
} catch (err) {
|
||||
if (err.name === 'TypeError') {
|
||||
return Object.prototype.toString.call(value);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
sameValue(actual, expected, message) {
|
||||
if (assert._isSameValue(actual, expected)) {
|
||||
return;
|
||||
}
|
||||
if (message === undefined) {
|
||||
message = '';
|
||||
} else {
|
||||
message += ' ';
|
||||
}
|
||||
|
||||
message += 'Expected SameValue(«' + assert._toString(actual) + '», «' + assert._toString(expected) + '») to be true';
|
||||
|
||||
throw new Error(message);
|
||||
},
|
||||
|
||||
throws(f, ctor, message) {
|
||||
if (message === undefined) {
|
||||
message = '';
|
||||
} else {
|
||||
message += ' ';
|
||||
}
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
if (e.constructor !== ctor) {
|
||||
throw new Error(message + "Wrong exception type was thrown: " + e.constructor.name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new Error(message + "No exception was thrown");
|
||||
},
|
||||
|
||||
throwsNodeError(f, ctor, code, message) {
|
||||
if (message === undefined) {
|
||||
message = '';
|
||||
} else {
|
||||
message += ' ';
|
||||
}
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
if (e.constructor !== ctor) {
|
||||
throw new Error(message + "Wrong exception type was thrown: " + e.constructor.name);
|
||||
}
|
||||
if (e.code !== code) {
|
||||
throw new Error(message + "Wrong exception code was thrown: " + e.code);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new Error(message + "No exception was thrown");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = assert;
|
440
goja_nodejs/buffer/buffer.go
Normal file
440
goja_nodejs/buffer/buffer.go
Normal file
@ -0,0 +1,440 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/errors"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
|
||||
"github.com/dop251/base64dec"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
const ModuleName = "buffer"
|
||||
|
||||
type Buffer struct {
|
||||
r *goja.Runtime
|
||||
|
||||
bufferCtorObj *goja.Object
|
||||
|
||||
uint8ArrayCtorObj *goja.Object
|
||||
uint8ArrayCtor goja.Constructor
|
||||
}
|
||||
|
||||
var (
|
||||
symApi = goja.NewSymbol("api")
|
||||
)
|
||||
|
||||
var (
|
||||
reflectTypeArrayBuffer = reflect.TypeOf(goja.ArrayBuffer{})
|
||||
reflectTypeString = reflect.TypeOf("")
|
||||
reflectTypeInt = reflect.TypeOf(int64(0))
|
||||
reflectTypeFloat = reflect.TypeOf(0.0)
|
||||
reflectTypeBytes = reflect.TypeOf(([]byte)(nil))
|
||||
)
|
||||
|
||||
func Enable(runtime *goja.Runtime) {
|
||||
runtime.Set("Buffer", require.Require(runtime, ModuleName).ToObject(runtime).Get("Buffer"))
|
||||
}
|
||||
|
||||
func Bytes(r *goja.Runtime, v goja.Value) []byte {
|
||||
var b []byte
|
||||
err := r.ExportTo(v, &b)
|
||||
if err != nil {
|
||||
return []byte(v.String())
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func mod(r *goja.Runtime) *goja.Object {
|
||||
res := r.Get("Buffer")
|
||||
if res == nil {
|
||||
res = require.Require(r, ModuleName).ToObject(r).Get("Buffer")
|
||||
}
|
||||
m, ok := res.(*goja.Object)
|
||||
if !ok {
|
||||
panic(r.NewTypeError("Could not extract Buffer"))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func api(mod *goja.Object) *Buffer {
|
||||
if s := mod.GetSymbol(symApi); s != nil {
|
||||
b, _ := s.Export().(*Buffer)
|
||||
return b
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetApi(r *goja.Runtime) *Buffer {
|
||||
return api(mod(r))
|
||||
}
|
||||
|
||||
func DecodeBytes(r *goja.Runtime, arg, enc goja.Value) []byte {
|
||||
switch arg.ExportType() {
|
||||
case reflectTypeArrayBuffer:
|
||||
return arg.Export().(goja.ArrayBuffer).Bytes()
|
||||
case reflectTypeString:
|
||||
var codec StringCodec
|
||||
if !goja.IsUndefined(enc) {
|
||||
codec = stringCodecs[enc.String()]
|
||||
}
|
||||
if codec == nil {
|
||||
codec = utf8Codec
|
||||
}
|
||||
return codec.DecodeAppend(arg.String(), nil)
|
||||
default:
|
||||
if o, ok := arg.(*goja.Object); ok {
|
||||
if o.ExportType() == reflectTypeBytes {
|
||||
return o.Export().([]byte)
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"data\" argument must be of type string or an instance of Buffer, TypedArray, or DataView."))
|
||||
}
|
||||
|
||||
func WrapBytes(r *goja.Runtime, data []byte) *goja.Object {
|
||||
m := mod(r)
|
||||
if api := api(m); api != nil {
|
||||
return api.WrapBytes(data)
|
||||
}
|
||||
if from, ok := goja.AssertFunction(m.Get("from")); ok {
|
||||
ab := r.NewArrayBuffer(data)
|
||||
v, err := from(m, r.ToValue(ab))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v.ToObject(r)
|
||||
}
|
||||
panic(r.NewTypeError("Buffer.from is not a function"))
|
||||
}
|
||||
|
||||
// EncodeBytes returns the given byte slice encoded as string with the given encoding. If encoding
|
||||
// is not specified or not supported, returns a Buffer that wraps the data.
|
||||
func EncodeBytes(r *goja.Runtime, data []byte, enc goja.Value) goja.Value {
|
||||
var codec StringCodec
|
||||
if !goja.IsUndefined(enc) {
|
||||
codec = StringCodecByName(enc.String())
|
||||
}
|
||||
if codec != nil {
|
||||
return r.ToValue(codec.Encode(data))
|
||||
}
|
||||
return WrapBytes(r, data)
|
||||
}
|
||||
|
||||
func (b *Buffer) WrapBytes(data []byte) *goja.Object {
|
||||
return b.fromBytes(data)
|
||||
}
|
||||
|
||||
func (b *Buffer) ctor(call goja.ConstructorCall) (res *goja.Object) {
|
||||
arg := call.Argument(0)
|
||||
switch arg.ExportType() {
|
||||
case reflectTypeInt, reflectTypeFloat:
|
||||
panic(b.r.NewTypeError("Calling the Buffer constructor with numeric argument is not implemented yet"))
|
||||
// TODO implement
|
||||
}
|
||||
return b._from(call.Arguments...)
|
||||
}
|
||||
|
||||
type StringCodec interface {
|
||||
DecodeAppend(string, []byte) []byte
|
||||
Encode([]byte) string
|
||||
}
|
||||
|
||||
type hexCodec struct{}
|
||||
|
||||
func (hexCodec) DecodeAppend(s string, b []byte) []byte {
|
||||
l := hex.DecodedLen(len(s))
|
||||
dst, res := expandSlice(b, l)
|
||||
n, err := hex.Decode(dst, []byte(s))
|
||||
if err != nil {
|
||||
res = res[:len(b)+n]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (hexCodec) Encode(b []byte) string {
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
type _utf8Codec struct{}
|
||||
|
||||
func (_utf8Codec) DecodeAppend(s string, b []byte) []byte {
|
||||
r, _ := unicode.UTF8.NewEncoder().String(s)
|
||||
dst, res := expandSlice(b, len(r))
|
||||
copy(dst, r)
|
||||
return res
|
||||
}
|
||||
|
||||
func (_utf8Codec) Encode(b []byte) string {
|
||||
r, _ := unicode.UTF8.NewDecoder().Bytes(b)
|
||||
return string(r)
|
||||
}
|
||||
|
||||
type base64Codec struct{}
|
||||
|
||||
type base64UrlCodec struct {
|
||||
base64Codec
|
||||
}
|
||||
|
||||
func (base64Codec) DecodeAppend(s string, b []byte) []byte {
|
||||
res, _ := Base64DecodeAppend(b, s)
|
||||
return res
|
||||
}
|
||||
|
||||
func (base64Codec) Encode(b []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func (base64UrlCodec) Encode(b []byte) string {
|
||||
return base64.RawURLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
var utf8Codec StringCodec = _utf8Codec{}
|
||||
|
||||
var stringCodecs = map[string]StringCodec{
|
||||
"hex": hexCodec{},
|
||||
"utf8": utf8Codec,
|
||||
"utf-8": utf8Codec,
|
||||
"base64": base64Codec{},
|
||||
"base64Url": base64UrlCodec{},
|
||||
}
|
||||
|
||||
func expandSlice(b []byte, l int) (dst, res []byte) {
|
||||
if cap(b)-len(b) < l {
|
||||
b1 := make([]byte, len(b)+l)
|
||||
copy(b1, b)
|
||||
dst = b1[len(b):]
|
||||
res = b1
|
||||
} else {
|
||||
dst = b[len(b) : len(b)+l]
|
||||
res = b[:len(b)+l]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Base64DecodeAppend(dst []byte, src string) ([]byte, error) {
|
||||
l := base64.RawStdEncoding.DecodedLen(len(src))
|
||||
d, res := expandSlice(dst, l)
|
||||
n, err := base64dec.DecodeBase64(d, src)
|
||||
|
||||
res = res[:len(dst)+n]
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (b *Buffer) fromString(str, enc string) *goja.Object {
|
||||
codec := stringCodecs[enc]
|
||||
if codec == nil {
|
||||
codec = utf8Codec
|
||||
}
|
||||
return b.fromBytes(codec.DecodeAppend(str, nil))
|
||||
}
|
||||
|
||||
func (b *Buffer) fromBytes(data []byte) *goja.Object {
|
||||
o, err := b.uint8ArrayCtor(b.bufferCtorObj, b.r.ToValue(b.r.NewArrayBuffer(data)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (b *Buffer) _from(args ...goja.Value) *goja.Object {
|
||||
if len(args) == 0 {
|
||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined"))
|
||||
}
|
||||
arg := args[0]
|
||||
switch arg.ExportType() {
|
||||
case reflectTypeArrayBuffer:
|
||||
v, err := b.uint8ArrayCtor(b.bufferCtorObj, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
case reflectTypeString:
|
||||
var enc string
|
||||
if len(args) > 1 {
|
||||
enc = args[1].String()
|
||||
}
|
||||
return b.fromString(arg.String(), enc)
|
||||
default:
|
||||
if o, ok := arg.(*goja.Object); ok {
|
||||
if o.ExportType() == reflectTypeBytes {
|
||||
bb, _ := o.Export().([]byte)
|
||||
a := make([]byte, len(bb))
|
||||
copy(a, bb)
|
||||
return b.fromBytes(a)
|
||||
} else {
|
||||
if f, ok := goja.AssertFunction(o.Get("valueOf")); ok {
|
||||
valueOf, err := f(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if valueOf != o {
|
||||
args[0] = valueOf
|
||||
return b._from(args...)
|
||||
}
|
||||
}
|
||||
|
||||
if s := o.GetSymbol(goja.SymToPrimitive); s != nil {
|
||||
if f, ok := goja.AssertFunction(s); ok {
|
||||
str, err := f(o, b.r.ToValue("string"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
args[0] = str
|
||||
return b._from(args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
// array-like
|
||||
if v := o.Get("length"); v != nil {
|
||||
length := int(v.ToInteger())
|
||||
a := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
item := o.Get(strconv.Itoa(i))
|
||||
if item != nil {
|
||||
a[i] = byte(item.ToInteger())
|
||||
}
|
||||
}
|
||||
return b.fromBytes(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received %s", arg))
|
||||
}
|
||||
|
||||
func (b *Buffer) from(call goja.FunctionCall) goja.Value {
|
||||
return b._from(call.Arguments...)
|
||||
}
|
||||
|
||||
func isNumber(v goja.Value) bool {
|
||||
switch v.ExportType() {
|
||||
case reflectTypeInt, reflectTypeFloat:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isString(v goja.Value) bool {
|
||||
return v.ExportType() == reflectTypeString
|
||||
}
|
||||
|
||||
func StringCodecByName(name string) StringCodec {
|
||||
return stringCodecs[name]
|
||||
}
|
||||
|
||||
func (b *Buffer) getStringCodec(enc goja.Value) (codec StringCodec) {
|
||||
if !goja.IsUndefined(enc) {
|
||||
codec = stringCodecs[enc.String()]
|
||||
if codec == nil {
|
||||
panic(errors.NewTypeError(b.r, "ERR_UNKNOWN_ENCODING", "Unknown encoding: %s", enc))
|
||||
}
|
||||
} else {
|
||||
codec = utf8Codec
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Buffer) fill(buf []byte, fill string, enc goja.Value) []byte {
|
||||
codec := b.getStringCodec(enc)
|
||||
b1 := codec.DecodeAppend(fill, buf[:0])
|
||||
if len(b1) > len(buf) {
|
||||
return b1[:len(buf)]
|
||||
}
|
||||
for i := len(b1); i < len(buf); {
|
||||
i += copy(buf[i:], buf[:i])
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
|
||||
arg0 := call.Argument(0)
|
||||
size := -1
|
||||
if isNumber(arg0) {
|
||||
size = int(arg0.ToInteger())
|
||||
}
|
||||
if size < 0 {
|
||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"size\" argument must be of type number."))
|
||||
}
|
||||
fill := call.Argument(1)
|
||||
buf := make([]byte, size)
|
||||
if !goja.IsUndefined(fill) {
|
||||
if isString(fill) {
|
||||
var enc goja.Value
|
||||
if a := call.Argument(2); isString(a) {
|
||||
enc = a
|
||||
} else {
|
||||
enc = goja.Undefined()
|
||||
}
|
||||
buf = b.fill(buf, fill.String(), enc)
|
||||
} else {
|
||||
fill = fill.ToNumber()
|
||||
if !goja.IsNaN(fill) && !goja.IsInfinity(fill) {
|
||||
fillByte := byte(fill.ToInteger())
|
||||
if fillByte != 0 {
|
||||
for i := range buf {
|
||||
buf[i] = fillByte
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.fromBytes(buf)
|
||||
}
|
||||
|
||||
func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value {
|
||||
bb := Bytes(b.r, call.This)
|
||||
codec := b.getStringCodec(call.Argument(0))
|
||||
return b.r.ToValue(codec.Encode(bb))
|
||||
}
|
||||
|
||||
func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
|
||||
bb := Bytes(b.r, call.This)
|
||||
other := call.Argument(0)
|
||||
if b.r.InstanceOf(other, b.uint8ArrayCtorObj) {
|
||||
otherBytes := Bytes(b.r, other)
|
||||
return b.r.ToValue(bytes.Equal(bb, otherBytes))
|
||||
}
|
||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array."))
|
||||
}
|
||||
|
||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||
b := &Buffer{r: runtime}
|
||||
uint8Array := runtime.Get("Uint8Array")
|
||||
if c, ok := goja.AssertConstructor(uint8Array); ok {
|
||||
b.uint8ArrayCtor = c
|
||||
} else {
|
||||
panic(runtime.NewTypeError("Uint8Array is not a constructor"))
|
||||
}
|
||||
uint8ArrayObj := uint8Array.ToObject(runtime)
|
||||
|
||||
ctor := runtime.ToValue(b.ctor).ToObject(runtime)
|
||||
ctor.SetPrototype(uint8ArrayObj)
|
||||
ctor.DefineDataPropertySymbol(symApi, runtime.ToValue(b), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
|
||||
b.bufferCtorObj = ctor
|
||||
b.uint8ArrayCtorObj = uint8ArrayObj
|
||||
|
||||
proto := runtime.NewObject()
|
||||
proto.SetPrototype(uint8ArrayObj.Get("prototype").ToObject(runtime))
|
||||
proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
|
||||
proto.Set("equals", b.proto_equals)
|
||||
proto.Set("toString", b.proto_toString)
|
||||
|
||||
ctor.Set("prototype", proto)
|
||||
ctor.Set("poolSize", 8192)
|
||||
ctor.Set("from", b.from)
|
||||
ctor.Set("alloc", b.alloc)
|
||||
|
||||
exports := module.Get("exports").(*goja.Object)
|
||||
exports.Set("Buffer", ctor)
|
||||
}
|
||||
|
||||
func init() {
|
||||
require.RegisterCoreModule(ModuleName, Require)
|
||||
}
|
228
goja_nodejs/buffer/buffer_test.go
Normal file
228
goja_nodejs/buffer/buffer_test.go
Normal file
@ -0,0 +1,228 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func TestBufferFrom(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
const Buffer = require("node:buffer").Buffer;
|
||||
|
||||
function checkBuffer(buf) {
|
||||
if (!(buf instanceof Buffer)) {
|
||||
throw new Error("instanceof Buffer");
|
||||
}
|
||||
|
||||
if (!(buf instanceof Uint8Array)) {
|
||||
throw new Error("instanceof Uint8Array");
|
||||
}
|
||||
}
|
||||
|
||||
checkBuffer(Buffer.from(new ArrayBuffer(16)));
|
||||
checkBuffer(Buffer.from(new Uint16Array(8)));
|
||||
|
||||
{
|
||||
const b = Buffer.from("\xff\xfe\xfd");
|
||||
const h = b.toString("hex")
|
||||
if (h !== "c3bfc3bec3bd") {
|
||||
throw new Error(h);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.from("0102fffdXXX", "hex");
|
||||
checkBuffer(b);
|
||||
if (b.toString("hex") !== "0102fffd") {
|
||||
throw new Error(b.toString("hex"));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.from('1ag123', 'hex');
|
||||
if (b.length !== 1 || b[0] !== 0x1a) {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.from('1a7', 'hex');
|
||||
if (b.length !== 1 || b[0] !== 0x1a) {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.from("\uD801", "utf-8");
|
||||
if (b.length !== 3 || b[0] !== 0xef || b[1] !== 0xbf || b[2] !== 0xbd) {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromBase64(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
const Buffer = require("node:buffer").Buffer;
|
||||
|
||||
{
|
||||
let b = Buffer.from("AAA_", "base64");
|
||||
if (b.length !== 3 || b[0] !== 0 || b[1] !== 0 || b[2] !== 0x3f) {
|
||||
throw new Error(b.toString("hex"));
|
||||
}
|
||||
|
||||
let r = b.toString("base64");
|
||||
if (r !== "AAA/") {
|
||||
throw new Error("to base64: " + r);
|
||||
}
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let s = "A".repeat(i) + "_" + "A".repeat(20-i);
|
||||
let s1 = "A".repeat(i) + "/" + "A".repeat(20-i);
|
||||
let b = Buffer.from(s, "base64");
|
||||
let b1 = Buffer.from(s1, "base64");
|
||||
if (!b.equals(b1)) {
|
||||
throw new Error(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let b = Buffer.from("SQ==???", "base64");
|
||||
if (b.length !== 1 || b[0] != 0x49) {
|
||||
throw new Error(b.toString("hex"));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let s = Buffer.from("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "base64Url").toString("base64");
|
||||
if (s !== "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ+EstJQLr/T+1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow==") {
|
||||
throw new Error(s);
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapBytes(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
b := []byte{1, 2, 3}
|
||||
buffer := GetApi(vm)
|
||||
vm.Set("b", buffer.WrapBytes(b))
|
||||
Enable(vm)
|
||||
_, err := vm.RunString(`
|
||||
if (typeof Buffer !== "function") {
|
||||
throw new Error("Buffer is not a function: " + typeof Buffer);
|
||||
}
|
||||
if (!(b instanceof Buffer)) {
|
||||
throw new Error("instanceof Buffer");
|
||||
}
|
||||
if (b.toString("hex") !== "010203") {
|
||||
throw new Error(b);
|
||||
}
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_alloc(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
const Buffer = require("node:buffer").Buffer;
|
||||
|
||||
{
|
||||
const b = Buffer.alloc(2, "abc");
|
||||
if (b.toString() !== "ab") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.alloc(16, "abc");
|
||||
if (b.toString() !== "abcabcabcabcabca") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const fill = {
|
||||
valueOf() {
|
||||
return 0xac;
|
||||
}
|
||||
}
|
||||
const b = Buffer.alloc(8, fill);
|
||||
if (b.toString("hex") !== "acacacacacacacac") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const fill = {
|
||||
valueOf() {
|
||||
return Infinity;
|
||||
}
|
||||
}
|
||||
const b = Buffer.alloc(2, fill);
|
||||
if (b.toString("hex") !== "0000") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const fill = {
|
||||
valueOf() {
|
||||
return "ac";
|
||||
}
|
||||
}
|
||||
const b = Buffer.alloc(2, fill);
|
||||
if (b.toString("hex") !== "0000") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.alloc(2, -257.4);
|
||||
if (b.toString("hex") !== "ffff") {
|
||||
throw new Error(b);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.alloc(2, Infinity);
|
||||
if (b.toString("hex") !== "0000") {
|
||||
throw new Error("Infinity: " + b.toString("hex"));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const b = Buffer.alloc(2, null);
|
||||
if (b.toString("hex") !== "0000") {
|
||||
throw new Error("Infinity: " + b.toString("hex"));
|
||||
}
|
||||
}
|
||||
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
72
goja_nodejs/console/module.go
Normal file
72
goja_nodejs/console/module.go
Normal file
@ -0,0 +1,72 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
"apigo.cc/ai/ai/goja_nodejs/util"
|
||||
)
|
||||
|
||||
const ModuleName = "console"
|
||||
|
||||
type Console struct {
|
||||
runtime *goja.Runtime
|
||||
util *goja.Object
|
||||
printer Printer
|
||||
}
|
||||
|
||||
type Printer interface {
|
||||
Log(string)
|
||||
Warn(string)
|
||||
Error(string)
|
||||
}
|
||||
|
||||
func (c *Console) log(p func(string)) func(goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if format, ok := goja.AssertFunction(c.util.Get("format")); ok {
|
||||
ret, err := format(c.util, call.Arguments...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p(ret.String())
|
||||
} else {
|
||||
panic(c.runtime.NewTypeError("util.format is not a function"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||
requireWithPrinter(defaultStdPrinter)(runtime, module)
|
||||
}
|
||||
|
||||
func RequireWithPrinter(printer Printer) require.ModuleLoader {
|
||||
return requireWithPrinter(printer)
|
||||
}
|
||||
|
||||
func requireWithPrinter(printer Printer) require.ModuleLoader {
|
||||
return func(runtime *goja.Runtime, module *goja.Object) {
|
||||
c := &Console{
|
||||
runtime: runtime,
|
||||
printer: printer,
|
||||
}
|
||||
|
||||
c.util = require.Require(runtime, util.ModuleName).(*goja.Object)
|
||||
|
||||
o := module.Get("exports").(*goja.Object)
|
||||
o.Set("log", c.log(c.printer.Log))
|
||||
o.Set("error", c.log(c.printer.Error))
|
||||
o.Set("warn", c.log(c.printer.Warn))
|
||||
o.Set("info", c.log(c.printer.Log))
|
||||
o.Set("debug", c.log(c.printer.Log))
|
||||
}
|
||||
}
|
||||
|
||||
func Enable(runtime *goja.Runtime) {
|
||||
runtime.Set("console", require.Require(runtime, ModuleName))
|
||||
}
|
||||
|
||||
func init() {
|
||||
require.RegisterCoreModule(ModuleName, Require)
|
||||
}
|
78
goja_nodejs/console/module_test.go
Normal file
78
goja_nodejs/console/module_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func TestConsole(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("console"); c == nil {
|
||||
t.Fatal("console not found")
|
||||
}
|
||||
|
||||
if _, err := vm.RunString("console.log('')"); err != nil {
|
||||
t.Fatal("console.log() error", err)
|
||||
}
|
||||
|
||||
if _, err := vm.RunString("console.error('')"); err != nil {
|
||||
t.Fatal("console.error() error", err)
|
||||
}
|
||||
|
||||
if _, err := vm.RunString("console.warn('')"); err != nil {
|
||||
t.Fatal("console.warn() error", err)
|
||||
}
|
||||
|
||||
if _, err := vm.RunString("console.info('')"); err != nil {
|
||||
t.Fatal("console.info() error", err)
|
||||
}
|
||||
|
||||
if _, err := vm.RunString("console.debug('')"); err != nil {
|
||||
t.Fatal("console.debug() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsoleWithPrinter(t *testing.T) {
|
||||
var stdoutStr, stderrStr string
|
||||
|
||||
printer := StdPrinter{
|
||||
StdoutPrint: func(s string) { stdoutStr += s },
|
||||
StderrPrint: func(s string) { stderrStr += s },
|
||||
}
|
||||
|
||||
vm := goja.New()
|
||||
|
||||
registry := new(require.Registry)
|
||||
registry.Enable(vm)
|
||||
registry.RegisterNativeModule(ModuleName, RequireWithPrinter(printer))
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("console"); c == nil {
|
||||
t.Fatal("console not found")
|
||||
}
|
||||
|
||||
_, err := vm.RunString(`
|
||||
console.log('a')
|
||||
console.error('b')
|
||||
console.warn('c')
|
||||
console.debug('d')
|
||||
console.info('e')
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want := "ade"; stdoutStr != want {
|
||||
t.Fatalf("Unexpected stdout output: got %q, want %q", stdoutStr, want)
|
||||
}
|
||||
|
||||
if want := "bc"; stderrStr != want {
|
||||
t.Fatalf("Unexpected stderr output: got %q, want %q", stderrStr, want)
|
||||
}
|
||||
}
|
38
goja_nodejs/console/std_printer.go
Normal file
38
goja_nodejs/console/std_printer.go
Normal file
@ -0,0 +1,38 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
stderrLogger = log.Default() // the default logger output to stderr
|
||||
stdoutLogger = log.New(os.Stdout, "", log.LstdFlags)
|
||||
|
||||
defaultStdPrinter Printer = &StdPrinter{
|
||||
StdoutPrint: func(s string) { stdoutLogger.Print(s) },
|
||||
StderrPrint: func(s string) { stderrLogger.Print(s) },
|
||||
}
|
||||
)
|
||||
|
||||
// StdPrinter implements the console.Printer interface
|
||||
// that prints to the stdout or stderr.
|
||||
type StdPrinter struct {
|
||||
StdoutPrint func(s string)
|
||||
StderrPrint func(s string)
|
||||
}
|
||||
|
||||
// Log prints s to the stdout.
|
||||
func (p StdPrinter) Log(s string) {
|
||||
p.StdoutPrint(s)
|
||||
}
|
||||
|
||||
// Warn prints s to the stderr.
|
||||
func (p StdPrinter) Warn(s string) {
|
||||
p.StderrPrint(s)
|
||||
}
|
||||
|
||||
// Error prints s to the stderr.
|
||||
func (p StdPrinter) Error(s string) {
|
||||
p.StderrPrint(s)
|
||||
}
|
71
goja_nodejs/errors/errors.go
Normal file
71
goja_nodejs/errors/errors.go
Normal file
@ -0,0 +1,71 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeInvalidArgType = "ERR_INVALID_ARG_TYPE"
|
||||
ErrCodeInvalidArgValue = "ERR_INVALID_ARG_VALUE"
|
||||
ErrCodeInvalidThis = "ERR_INVALID_THIS"
|
||||
ErrCodeMissingArgs = "ERR_MISSING_ARGS"
|
||||
)
|
||||
|
||||
func error_toString(call goja.FunctionCall, r *goja.Runtime) goja.Value {
|
||||
this := call.This.ToObject(r)
|
||||
var name, msg string
|
||||
if n := this.Get("name"); n != nil && !goja.IsUndefined(n) {
|
||||
name = n.String()
|
||||
} else {
|
||||
name = "Error"
|
||||
}
|
||||
if m := this.Get("message"); m != nil && !goja.IsUndefined(m) {
|
||||
msg = m.String()
|
||||
}
|
||||
if code := this.Get("code"); code != nil && !goja.IsUndefined(code) {
|
||||
if name != "" {
|
||||
name += " "
|
||||
}
|
||||
name += "[" + code.String() + "]"
|
||||
}
|
||||
if msg != "" {
|
||||
if name != "" {
|
||||
name += ": "
|
||||
}
|
||||
name += msg
|
||||
}
|
||||
return r.ToValue(name)
|
||||
}
|
||||
|
||||
func addProps(r *goja.Runtime, e *goja.Object, code string) {
|
||||
e.Set("code", code)
|
||||
e.DefineDataProperty("toString", r.ToValue(error_toString), goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
|
||||
}
|
||||
|
||||
func NewTypeError(r *goja.Runtime, code string, params ...interface{}) *goja.Object {
|
||||
e := r.NewTypeError(params...)
|
||||
addProps(r, e, code)
|
||||
return e
|
||||
}
|
||||
|
||||
func NewError(r *goja.Runtime, ctor *goja.Object, code string, args ...interface{}) *goja.Object {
|
||||
if ctor == nil {
|
||||
ctor, _ = r.Get("Error").(*goja.Object)
|
||||
}
|
||||
if ctor == nil {
|
||||
return nil
|
||||
}
|
||||
msg := ""
|
||||
if len(args) > 0 {
|
||||
f, _ := args[0].(string)
|
||||
msg = fmt.Sprintf(f, args[1:]...)
|
||||
}
|
||||
o, err := r.New(ctor, r.ToValue(msg))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
addProps(r, o, code)
|
||||
return o
|
||||
}
|
514
goja_nodejs/eventloop/eventloop.go
Normal file
514
goja_nodejs/eventloop/eventloop.go
Normal file
@ -0,0 +1,514 @@
|
||||
package eventloop
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/console"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
type job struct {
|
||||
cancel func() bool
|
||||
fn func()
|
||||
idx int
|
||||
|
||||
cancelled bool
|
||||
}
|
||||
|
||||
type Timer struct {
|
||||
job
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
type Interval struct {
|
||||
job
|
||||
ticker *time.Ticker
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
type Immediate struct {
|
||||
job
|
||||
}
|
||||
|
||||
type EventLoop struct {
|
||||
vm *goja.Runtime
|
||||
jobChan chan func()
|
||||
jobs []*job
|
||||
jobCount int32
|
||||
canRun int32
|
||||
|
||||
auxJobsLock sync.Mutex
|
||||
wakeupChan chan struct{}
|
||||
|
||||
auxJobsSpare, auxJobs []func()
|
||||
|
||||
stopLock sync.Mutex
|
||||
stopCond *sync.Cond
|
||||
running bool
|
||||
terminated bool
|
||||
|
||||
enableConsole bool
|
||||
registry *require.Registry
|
||||
}
|
||||
|
||||
func NewEventLoop(opts ...Option) *EventLoop {
|
||||
vm := goja.New()
|
||||
|
||||
loop := &EventLoop{
|
||||
vm: vm,
|
||||
jobChan: make(chan func()),
|
||||
wakeupChan: make(chan struct{}, 1),
|
||||
enableConsole: true,
|
||||
}
|
||||
loop.stopCond = sync.NewCond(&loop.stopLock)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(loop)
|
||||
}
|
||||
if loop.registry == nil {
|
||||
loop.registry = new(require.Registry)
|
||||
}
|
||||
loop.registry.Enable(vm)
|
||||
if loop.enableConsole {
|
||||
console.Enable(vm)
|
||||
}
|
||||
vm.Set("setTimeout", loop.setTimeout)
|
||||
vm.Set("setInterval", loop.setInterval)
|
||||
vm.Set("setImmediate", loop.setImmediate)
|
||||
vm.Set("clearTimeout", loop.clearTimeout)
|
||||
vm.Set("clearInterval", loop.clearInterval)
|
||||
vm.Set("clearImmediate", loop.clearImmediate)
|
||||
|
||||
return loop
|
||||
}
|
||||
|
||||
type Option func(*EventLoop)
|
||||
|
||||
// EnableConsole controls whether the "console" module is loaded into
|
||||
// the runtime used by the loop. By default, loops are created with
|
||||
// the "console" module loaded, pass EnableConsole(false) to
|
||||
// NewEventLoop to disable this behavior.
|
||||
func EnableConsole(enableConsole bool) Option {
|
||||
return func(loop *EventLoop) {
|
||||
loop.enableConsole = enableConsole
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegistry(registry *require.Registry) Option {
|
||||
return func(loop *EventLoop) {
|
||||
loop.registry = registry
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) schedule(call goja.FunctionCall, repeating bool) goja.Value {
|
||||
if fn, ok := goja.AssertFunction(call.Argument(0)); ok {
|
||||
delay := call.Argument(1).ToInteger()
|
||||
var args []goja.Value
|
||||
if len(call.Arguments) > 2 {
|
||||
args = append(args, call.Arguments[2:]...)
|
||||
}
|
||||
f := func() { fn(nil, args...) }
|
||||
loop.jobCount++
|
||||
var job *job
|
||||
var ret goja.Value
|
||||
if repeating {
|
||||
interval := loop.newInterval(f)
|
||||
interval.start(loop, time.Duration(delay)*time.Millisecond)
|
||||
job = &interval.job
|
||||
ret = loop.vm.ToValue(interval)
|
||||
} else {
|
||||
timeout := loop.newTimeout(f)
|
||||
timeout.start(loop, time.Duration(delay)*time.Millisecond)
|
||||
job = &timeout.job
|
||||
ret = loop.vm.ToValue(timeout)
|
||||
}
|
||||
job.idx = len(loop.jobs)
|
||||
loop.jobs = append(loop.jobs, job)
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setTimeout(call goja.FunctionCall) goja.Value {
|
||||
return loop.schedule(call, false)
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setInterval(call goja.FunctionCall) goja.Value {
|
||||
return loop.schedule(call, true)
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setImmediate(call goja.FunctionCall) goja.Value {
|
||||
if fn, ok := goja.AssertFunction(call.Argument(0)); ok {
|
||||
var args []goja.Value
|
||||
if len(call.Arguments) > 1 {
|
||||
args = append(args, call.Arguments[1:]...)
|
||||
}
|
||||
f := func() { fn(nil, args...) }
|
||||
loop.jobCount++
|
||||
return loop.vm.ToValue(loop.addImmediate(f))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTimeout schedules to run the specified function in the context
|
||||
// of the loop as soon as possible after the specified timeout period.
|
||||
// SetTimeout returns a Timer which can be passed to ClearTimeout.
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived
|
||||
// from it must not be used outside the function. SetTimeout is
|
||||
// safe to call inside or outside the loop.
|
||||
// If the loop is terminated (see Terminate()) returns nil.
|
||||
func (loop *EventLoop) SetTimeout(fn func(*goja.Runtime), timeout time.Duration) *Timer {
|
||||
t := loop.newTimeout(func() { fn(loop.vm) })
|
||||
if loop.addAuxJob(func() {
|
||||
t.start(loop, timeout)
|
||||
loop.jobCount++
|
||||
t.idx = len(loop.jobs)
|
||||
loop.jobs = append(loop.jobs, &t.job)
|
||||
}) {
|
||||
return t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearTimeout cancels a Timer returned by SetTimeout if it has not run yet.
|
||||
// ClearTimeout is safe to call inside or outside the loop.
|
||||
func (loop *EventLoop) ClearTimeout(t *Timer) {
|
||||
loop.addAuxJob(func() {
|
||||
loop.clearTimeout(t)
|
||||
})
|
||||
}
|
||||
|
||||
// SetInterval schedules to repeatedly run the specified function in
|
||||
// the context of the loop as soon as possible after every specified
|
||||
// timeout period. SetInterval returns an Interval which can be
|
||||
// passed to ClearInterval. The instance of goja.Runtime that is passed to the
|
||||
// function and any Values derived from it must not be used outside
|
||||
// the function. SetInterval is safe to call inside or outside the
|
||||
// loop.
|
||||
// If the loop is terminated (see Terminate()) returns nil.
|
||||
func (loop *EventLoop) SetInterval(fn func(*goja.Runtime), timeout time.Duration) *Interval {
|
||||
i := loop.newInterval(func() { fn(loop.vm) })
|
||||
if loop.addAuxJob(func() {
|
||||
i.start(loop, timeout)
|
||||
loop.jobCount++
|
||||
i.idx = len(loop.jobs)
|
||||
loop.jobs = append(loop.jobs, &i.job)
|
||||
}) {
|
||||
return i
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearInterval cancels an Interval returned by SetInterval.
|
||||
// ClearInterval is safe to call inside or outside the loop.
|
||||
func (loop *EventLoop) ClearInterval(i *Interval) {
|
||||
loop.addAuxJob(func() {
|
||||
loop.clearInterval(i)
|
||||
})
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setRunning() {
|
||||
loop.stopLock.Lock()
|
||||
defer loop.stopLock.Unlock()
|
||||
if loop.running {
|
||||
panic("Loop is already started")
|
||||
}
|
||||
loop.running = true
|
||||
atomic.StoreInt32(&loop.canRun, 1)
|
||||
loop.auxJobsLock.Lock()
|
||||
loop.terminated = false
|
||||
loop.auxJobsLock.Unlock()
|
||||
}
|
||||
|
||||
// Run calls the specified function, starts the event loop and waits until there are no more delayed jobs to run
|
||||
// after which it stops the loop and returns.
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used
|
||||
// outside the function.
|
||||
// Do NOT use this function while the loop is already running. Use RunOnLoop() instead.
|
||||
// If the loop is already started it will panic.
|
||||
func (loop *EventLoop) Run(fn func(*goja.Runtime)) {
|
||||
loop.setRunning()
|
||||
fn(loop.vm)
|
||||
loop.run(false)
|
||||
}
|
||||
|
||||
// Start the event loop in the background. The loop continues to run until Stop() is called.
|
||||
// If the loop is already started it will panic.
|
||||
func (loop *EventLoop) Start() {
|
||||
loop.setRunning()
|
||||
go loop.run(true)
|
||||
}
|
||||
|
||||
// StartInForeground starts the event loop in the current goroutine. The loop continues to run until Stop() is called.
|
||||
// If the loop is already started it will panic.
|
||||
// Use this instead of Start if you want to recover from panics that may occur while calling native Go functions from
|
||||
// within setInterval and setTimeout callbacks.
|
||||
func (loop *EventLoop) StartInForeground() {
|
||||
loop.setRunning()
|
||||
loop.run(true)
|
||||
}
|
||||
|
||||
// Stop the loop that was started with Start(). After this function returns there will be no more jobs executed
|
||||
// by the loop. It is possible to call Start() or Run() again after this to resume the execution.
|
||||
// Note, it does not cancel active timeouts (use Terminate() instead if you want this).
|
||||
// It is not allowed to run Start() (or Run()) and Stop() or Terminate() concurrently.
|
||||
// Calling Stop() on a non-running loop has no effect.
|
||||
// It is not allowed to call Stop() from the loop, because it is synchronous and cannot complete until the loop
|
||||
// is not running any jobs. Use StopNoWait() instead.
|
||||
// return number of jobs remaining
|
||||
func (loop *EventLoop) Stop() int {
|
||||
loop.stopLock.Lock()
|
||||
for loop.running {
|
||||
atomic.StoreInt32(&loop.canRun, 0)
|
||||
loop.wakeup()
|
||||
loop.stopCond.Wait()
|
||||
}
|
||||
loop.stopLock.Unlock()
|
||||
return int(loop.jobCount)
|
||||
}
|
||||
|
||||
// StopNoWait tells the loop to stop and returns immediately. Can be used inside the loop. Calling it on a
|
||||
// non-running loop has no effect.
|
||||
func (loop *EventLoop) StopNoWait() {
|
||||
loop.stopLock.Lock()
|
||||
if loop.running {
|
||||
atomic.StoreInt32(&loop.canRun, 0)
|
||||
loop.wakeup()
|
||||
}
|
||||
loop.stopLock.Unlock()
|
||||
}
|
||||
|
||||
// Terminate stops the loop and clears all active timeouts and intervals. After it returns there are no
|
||||
// active timers or goroutines associated with the loop. Any attempt to submit a task (by using RunOnLoop(),
|
||||
// SetTimeout() or SetInterval()) will not succeed.
|
||||
// After being terminated the loop can be restarted again by using Start() or Run().
|
||||
// This method must not be called concurrently with Stop*(), Start(), or Run().
|
||||
func (loop *EventLoop) Terminate() {
|
||||
loop.Stop()
|
||||
|
||||
loop.auxJobsLock.Lock()
|
||||
loop.terminated = true
|
||||
loop.auxJobsLock.Unlock()
|
||||
|
||||
loop.runAux()
|
||||
|
||||
for i := 0; i < len(loop.jobs); i++ {
|
||||
job := loop.jobs[i]
|
||||
if !job.cancelled {
|
||||
job.cancelled = true
|
||||
if job.cancel() {
|
||||
loop.removeJob(job)
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for len(loop.jobs) > 0 {
|
||||
(<-loop.jobChan)()
|
||||
}
|
||||
}
|
||||
|
||||
// RunOnLoop schedules to run the specified function in the context of the loop as soon as possible.
|
||||
// The order of the runs is preserved (i.e. the functions will be called in the same order as calls to RunOnLoop())
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used
|
||||
// outside the function. It is safe to call inside or outside the loop.
|
||||
// Returns true on success or false if the loop is terminated (see Terminate()).
|
||||
func (loop *EventLoop) RunOnLoop(fn func(*goja.Runtime)) bool {
|
||||
return loop.addAuxJob(func() { fn(loop.vm) })
|
||||
}
|
||||
|
||||
func (loop *EventLoop) runAux() {
|
||||
loop.auxJobsLock.Lock()
|
||||
jobs := loop.auxJobs
|
||||
loop.auxJobs = loop.auxJobsSpare
|
||||
loop.auxJobsLock.Unlock()
|
||||
for i, job := range jobs {
|
||||
job()
|
||||
jobs[i] = nil
|
||||
}
|
||||
loop.auxJobsSpare = jobs[:0]
|
||||
}
|
||||
|
||||
func (loop *EventLoop) run(inBackground bool) {
|
||||
loop.runAux()
|
||||
if inBackground {
|
||||
loop.jobCount++
|
||||
}
|
||||
LOOP:
|
||||
for loop.jobCount > 0 {
|
||||
select {
|
||||
case job := <-loop.jobChan:
|
||||
job()
|
||||
case <-loop.wakeupChan:
|
||||
loop.runAux()
|
||||
if atomic.LoadInt32(&loop.canRun) == 0 {
|
||||
break LOOP
|
||||
}
|
||||
}
|
||||
}
|
||||
if inBackground {
|
||||
loop.jobCount--
|
||||
}
|
||||
|
||||
loop.stopLock.Lock()
|
||||
loop.running = false
|
||||
loop.stopLock.Unlock()
|
||||
loop.stopCond.Broadcast()
|
||||
}
|
||||
|
||||
func (loop *EventLoop) wakeup() {
|
||||
select {
|
||||
case loop.wakeupChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) addAuxJob(fn func()) bool {
|
||||
loop.auxJobsLock.Lock()
|
||||
if loop.terminated {
|
||||
loop.auxJobsLock.Unlock()
|
||||
return false
|
||||
}
|
||||
loop.auxJobs = append(loop.auxJobs, fn)
|
||||
loop.auxJobsLock.Unlock()
|
||||
loop.wakeup()
|
||||
return true
|
||||
}
|
||||
|
||||
func (loop *EventLoop) newTimeout(f func()) *Timer {
|
||||
t := &Timer{
|
||||
job: job{fn: f},
|
||||
}
|
||||
t.cancel = t.doCancel
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Timer) start(loop *EventLoop, timeout time.Duration) {
|
||||
t.timer = time.AfterFunc(timeout, func() {
|
||||
loop.jobChan <- func() {
|
||||
loop.doTimeout(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (loop *EventLoop) newInterval(f func()) *Interval {
|
||||
i := &Interval{
|
||||
job: job{fn: f},
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
i.cancel = i.doCancel
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Interval) start(loop *EventLoop, timeout time.Duration) {
|
||||
// https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args
|
||||
if timeout <= 0 {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
i.ticker = time.NewTicker(timeout)
|
||||
go i.run(loop)
|
||||
}
|
||||
|
||||
func (loop *EventLoop) addImmediate(f func()) *Immediate {
|
||||
i := &Immediate{
|
||||
job: job{fn: f},
|
||||
}
|
||||
loop.addAuxJob(func() {
|
||||
loop.doImmediate(i)
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
func (loop *EventLoop) doTimeout(t *Timer) {
|
||||
loop.removeJob(&t.job)
|
||||
if !t.cancelled {
|
||||
t.cancelled = true
|
||||
loop.jobCount--
|
||||
t.fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) doInterval(i *Interval) {
|
||||
if !i.cancelled {
|
||||
i.fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) doImmediate(i *Immediate) {
|
||||
if !i.cancelled {
|
||||
i.cancelled = true
|
||||
loop.jobCount--
|
||||
i.fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearTimeout(t *Timer) {
|
||||
if t != nil && !t.cancelled {
|
||||
t.cancelled = true
|
||||
loop.jobCount--
|
||||
if t.doCancel() {
|
||||
loop.removeJob(&t.job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearInterval(i *Interval) {
|
||||
if i != nil && !i.cancelled {
|
||||
i.cancelled = true
|
||||
loop.jobCount--
|
||||
i.doCancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) removeJob(job *job) {
|
||||
idx := job.idx
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
if idx < len(loop.jobs)-1 {
|
||||
loop.jobs[idx] = loop.jobs[len(loop.jobs)-1]
|
||||
loop.jobs[idx].idx = idx
|
||||
}
|
||||
loop.jobs[len(loop.jobs)-1] = nil
|
||||
loop.jobs = loop.jobs[:len(loop.jobs)-1]
|
||||
job.idx = -1
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearImmediate(i *Immediate) {
|
||||
if i != nil && !i.cancelled {
|
||||
i.cancelled = true
|
||||
loop.jobCount--
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Interval) doCancel() bool {
|
||||
close(i.stopChan)
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Timer) doCancel() bool {
|
||||
return t.timer.Stop()
|
||||
}
|
||||
|
||||
func (i *Interval) run(loop *EventLoop) {
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-i.stopChan:
|
||||
i.ticker.Stop()
|
||||
break L
|
||||
case <-i.ticker.C:
|
||||
loop.jobChan <- func() {
|
||||
loop.doInterval(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
loop.jobChan <- func() {
|
||||
loop.removeJob(&i.job)
|
||||
}
|
||||
}
|
641
goja_nodejs/eventloop/eventloop_test.go
Normal file
641
goja_nodejs/eventloop/eventloop_test.go
Normal file
@ -0,0 +1,641 @@
|
||||
package eventloop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
var calledAt;
|
||||
setTimeout(function() {
|
||||
calledAt = now();
|
||||
}, 1000);
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Now()
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
vm.Set("now", time.Now)
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var calledAt time.Time
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calledAt.IsZero() {
|
||||
t.Fatal("Not called")
|
||||
}
|
||||
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||
t.Fatal(dur)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
var calledAt;
|
||||
setTimeout(function() {
|
||||
calledAt = now();
|
||||
}, 1000);
|
||||
`
|
||||
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loop := NewEventLoop()
|
||||
startTime := time.Now()
|
||||
loop.Start()
|
||||
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
vm.Set("now", time.Now)
|
||||
vm.RunProgram(prg)
|
||||
})
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
if remainingJobs := loop.Stop(); remainingJobs != 0 {
|
||||
t.Fatal(remainingJobs)
|
||||
}
|
||||
|
||||
var calledAt time.Time
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calledAt.IsZero() {
|
||||
t.Fatal("Not called")
|
||||
}
|
||||
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||
t.Fatal(dur)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartInForeground(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
var calledAt;
|
||||
setTimeout(function() {
|
||||
calledAt = now();
|
||||
}, 1000);
|
||||
`
|
||||
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loop := NewEventLoop()
|
||||
startTime := time.Now()
|
||||
go loop.StartInForeground()
|
||||
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
vm.Set("now", time.Now)
|
||||
vm.RunProgram(prg)
|
||||
})
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
if remainingJobs := loop.Stop(); remainingJobs != 0 {
|
||||
t.Fatal(remainingJobs)
|
||||
}
|
||||
|
||||
var calledAt time.Time
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calledAt.IsZero() {
|
||||
t.Fatal("Not called")
|
||||
}
|
||||
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||
t.Fatal(dur)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterval(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
var count = 0;
|
||||
var t = setInterval(function(times) {
|
||||
console.log("tick");
|
||||
if (++count > times) {
|
||||
clearInterval(t);
|
||||
}
|
||||
}, 1000, 2);
|
||||
console.log("Started");
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count int64
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
count = vm.Get("count").ToInteger()
|
||||
})
|
||||
if count != 3 {
|
||||
t.Fatal(count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmediate(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
let log = [];
|
||||
function cb(arg) {
|
||||
log.push(arg);
|
||||
}
|
||||
var i;
|
||||
var t = setImmediate(function() {
|
||||
cb("tick");
|
||||
setImmediate(cb, "tick 2");
|
||||
i = setImmediate(cb, "should not run")
|
||||
});
|
||||
setImmediate(function() {
|
||||
clearImmediate(i);
|
||||
});
|
||||
cb("Started");
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunString(`
|
||||
if (log.length != 3) {
|
||||
throw new Error("Invalid log length: " + log);
|
||||
}
|
||||
if (log[0] !== "Started" || log[1] !== "tick" || log[2] !== "tick 2") {
|
||||
throw new Error("Invalid log: " + log);
|
||||
}
|
||||
`)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNoSchedule(t *testing.T) {
|
||||
loop := NewEventLoop()
|
||||
fired := false
|
||||
loop.Run(func(vm *goja.Runtime) { // should not hang
|
||||
fired = true
|
||||
// do not schedule anything
|
||||
})
|
||||
|
||||
if !fired {
|
||||
t.Fatal("Not fired")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWithConsole(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
console.log("Started");
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Call to console.log generated an error", err)
|
||||
}
|
||||
|
||||
loop = NewEventLoop(EnableConsole(true))
|
||||
prg, err = goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Call to console.log generated an error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNoConsole(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
console.log("Started");
|
||||
`
|
||||
|
||||
loop := NewEventLoop(EnableConsole(false))
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Call to console.log did not generate an error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearIntervalRace(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
console.log("calling setInterval");
|
||||
var t = setInterval(function() {
|
||||
console.log("tick");
|
||||
}, 500);
|
||||
console.log("calling sleep");
|
||||
sleep(2000);
|
||||
console.log("calling clearInterval");
|
||||
clearInterval(t);
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Should not hang
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
vm.Set("sleep", func(ms int) {
|
||||
<-time.After(time.Duration(ms) * time.Millisecond)
|
||||
})
|
||||
vm.RunProgram(prg)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNativeTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
fired := false
|
||||
loop := NewEventLoop()
|
||||
loop.SetTimeout(func(*goja.Runtime) {
|
||||
fired = true
|
||||
}, 1*time.Second)
|
||||
loop.Run(func(*goja.Runtime) {
|
||||
// do not schedule anything
|
||||
})
|
||||
if !fired {
|
||||
t.Fatal("Not fired")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeClearTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
fired := false
|
||||
loop := NewEventLoop()
|
||||
timer := loop.SetTimeout(func(*goja.Runtime) {
|
||||
fired = true
|
||||
}, 2*time.Second)
|
||||
loop.SetTimeout(func(*goja.Runtime) {
|
||||
loop.ClearTimeout(timer)
|
||||
}, 1*time.Second)
|
||||
loop.Run(func(*goja.Runtime) {
|
||||
// do not schedule anything
|
||||
})
|
||||
if fired {
|
||||
t.Fatal("Cancelled timer fired!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeInterval(t *testing.T) {
|
||||
t.Parallel()
|
||||
count := 0
|
||||
loop := NewEventLoop()
|
||||
var i *Interval
|
||||
i = loop.SetInterval(func(*goja.Runtime) {
|
||||
t.Log("tick")
|
||||
count++
|
||||
if count > 2 {
|
||||
loop.ClearInterval(i)
|
||||
}
|
||||
}, 1*time.Second)
|
||||
loop.Run(func(*goja.Runtime) {
|
||||
// do not schedule anything
|
||||
})
|
||||
if count != 3 {
|
||||
t.Fatal("Expected interval to fire 3 times, got", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeClearInterval(t *testing.T) {
|
||||
t.Parallel()
|
||||
count := 0
|
||||
loop := NewEventLoop()
|
||||
loop.Run(func(*goja.Runtime) {
|
||||
i := loop.SetInterval(func(*goja.Runtime) {
|
||||
t.Log("tick")
|
||||
count++
|
||||
}, 500*time.Millisecond)
|
||||
<-time.After(2 * time.Second)
|
||||
loop.ClearInterval(i)
|
||||
})
|
||||
if count != 0 {
|
||||
t.Fatal("Expected interval to fire 0 times, got", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAndClearOnStoppedLoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
timeout := loop.SetTimeout(func(runtime *goja.Runtime) {
|
||||
panic("must not run")
|
||||
}, 1*time.Millisecond)
|
||||
loop.ClearTimeout(timeout)
|
||||
loop.Start()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
loop.Terminate()
|
||||
}
|
||||
|
||||
func TestSetTimeoutConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
loop.Start()
|
||||
ch := make(chan struct{}, 1)
|
||||
loop.SetTimeout(func(*goja.Runtime) {
|
||||
ch <- struct{}{}
|
||||
}, 100*time.Millisecond)
|
||||
<-ch
|
||||
loop.Stop()
|
||||
}
|
||||
|
||||
func TestClearTimeoutConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
loop.Start()
|
||||
timer := loop.SetTimeout(func(*goja.Runtime) {
|
||||
}, 100*time.Millisecond)
|
||||
loop.ClearTimeout(timer)
|
||||
loop.Stop()
|
||||
if c := loop.jobCount; c != 0 {
|
||||
t.Fatalf("jobCount: %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearIntervalConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
loop.Start()
|
||||
ch := make(chan struct{}, 1)
|
||||
i := loop.SetInterval(func(*goja.Runtime) {
|
||||
ch <- struct{}{}
|
||||
}, 500*time.Millisecond)
|
||||
|
||||
<-ch
|
||||
loop.ClearInterval(i)
|
||||
loop.Stop()
|
||||
if c := loop.jobCount; c != 0 {
|
||||
t.Fatalf("jobCount: %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunOnStoppedLoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
var failed int32
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for atomic.LoadInt32(&failed) == 0 {
|
||||
loop.Start()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
loop.Stop()
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for atomic.LoadInt32(&failed) == 0 {
|
||||
loop.RunOnLoop(func(*goja.Runtime) {
|
||||
if !loop.running {
|
||||
atomic.StoreInt32(&failed, 1)
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
if atomic.LoadInt32(&failed) != 0 {
|
||||
t.Fatal("running job on stopped loop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromise(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
let result;
|
||||
const p = new Promise((resolve, reject) => {
|
||||
setTimeout(() => {resolve("passed")}, 500);
|
||||
});
|
||||
p.then(value => {
|
||||
result = value;
|
||||
});
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
result := vm.Get("result")
|
||||
if !result.SameAs(vm.ToValue("passed")) {
|
||||
err = fmt.Errorf("unexpected result: %v", result)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromiseNative(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
let result;
|
||||
p.then(value => {
|
||||
result = value;
|
||||
done();
|
||||
});
|
||||
`
|
||||
|
||||
loop := NewEventLoop()
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ch := make(chan error)
|
||||
loop.Start()
|
||||
defer loop.Stop()
|
||||
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
vm.Set("done", func() {
|
||||
ch <- nil
|
||||
})
|
||||
p, resolve, _ := vm.NewPromise()
|
||||
vm.Set("p", p)
|
||||
_, err = vm.RunProgram(prg)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
loop.RunOnLoop(func(*goja.Runtime) {
|
||||
resolve("passed")
|
||||
})
|
||||
}()
|
||||
})
|
||||
err = <-ch
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
result := vm.Get("result")
|
||||
if !result.SameAs(vm.ToValue("passed")) {
|
||||
ch <- fmt.Errorf("unexpected result: %v", result)
|
||||
} else {
|
||||
ch <- nil
|
||||
}
|
||||
})
|
||||
err = <-ch
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventLoop_StopNoWait(t *testing.T) {
|
||||
t.Parallel()
|
||||
loop := NewEventLoop()
|
||||
var ran int32
|
||||
loop.Run(func(runtime *goja.Runtime) {
|
||||
loop.SetTimeout(func(*goja.Runtime) {
|
||||
atomic.StoreInt32(&ran, 1)
|
||||
}, 5*time.Second)
|
||||
|
||||
loop.SetTimeout(func(*goja.Runtime) {
|
||||
loop.StopNoWait()
|
||||
}, 500*time.Millisecond)
|
||||
})
|
||||
|
||||
if atomic.LoadInt32(&ran) != 0 {
|
||||
t.Fatal("ran != 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventLoop_ClearRunningTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
const SCRIPT = `
|
||||
var called = 0;
|
||||
let aTimer;
|
||||
function a() {
|
||||
if (++called > 5) {
|
||||
return;
|
||||
}
|
||||
if (aTimer) {
|
||||
clearTimeout(aTimer);
|
||||
}
|
||||
console.log("ok");
|
||||
aTimer = setTimeout(a, 500);
|
||||
}
|
||||
a();`
|
||||
|
||||
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loop := NewEventLoop()
|
||||
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var called int64
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
called = vm.Get("called").ToInteger()
|
||||
})
|
||||
if called != 6 {
|
||||
t.Fatal(called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventLoop_Terminate(t *testing.T) {
|
||||
defer goleak.VerifyNone(t)
|
||||
|
||||
loop := NewEventLoop()
|
||||
loop.Start()
|
||||
interval := loop.SetInterval(func(vm *goja.Runtime) {}, 10*time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
loop.ClearInterval(interval)
|
||||
loop.Terminate()
|
||||
|
||||
if loop.SetTimeout(func(*goja.Runtime) {}, time.Millisecond) != nil {
|
||||
t.Fatal("was able to SetTimeout()")
|
||||
}
|
||||
if loop.SetInterval(func(*goja.Runtime) {}, time.Millisecond) != nil {
|
||||
t.Fatal("was able to SetInterval()")
|
||||
}
|
||||
if loop.RunOnLoop(func(*goja.Runtime) {}) {
|
||||
t.Fatal("was able to RunOnLoop()")
|
||||
}
|
||||
|
||||
ch := make(chan struct{})
|
||||
loop.Start()
|
||||
if !loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||
close(ch)
|
||||
}) {
|
||||
t.Fatal("RunOnLoop() has failed after restart")
|
||||
}
|
||||
<-ch
|
||||
loop.Terminate()
|
||||
}
|
37
goja_nodejs/process/module.go
Normal file
37
goja_nodejs/process/module.go
Normal file
@ -0,0 +1,37 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
const ModuleName = "process"
|
||||
|
||||
type Process struct {
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||
p := &Process{
|
||||
env: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, e := range os.Environ() {
|
||||
envKeyValue := strings.SplitN(e, "=", 2)
|
||||
p.env[envKeyValue[0]] = envKeyValue[1]
|
||||
}
|
||||
|
||||
o := module.Get("exports").(*goja.Object)
|
||||
o.Set("env", p.env)
|
||||
}
|
||||
|
||||
func Enable(runtime *goja.Runtime) {
|
||||
runtime.Set("process", require.Require(runtime, ModuleName))
|
||||
}
|
||||
|
||||
func init() {
|
||||
require.RegisterCoreModule(ModuleName, Require)
|
||||
}
|
68
goja_nodejs/process/module_test.go
Normal file
68
goja_nodejs/process/module_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func TestProcessEnvStructure(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("process"); c == nil {
|
||||
t.Fatal("process not found")
|
||||
}
|
||||
|
||||
if c, err := vm.RunString("process.env"); c == nil || err != nil {
|
||||
t.Fatal("error accessing process.env")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessEnvValuesArtificial(t *testing.T) {
|
||||
os.Setenv("GOJA_IS_AWESOME", "true")
|
||||
defer os.Unsetenv("GOJA_IS_AWESOME")
|
||||
|
||||
vm := goja.New()
|
||||
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
jsRes, err := vm.RunString("process.env['GOJA_IS_AWESOME']")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error executing: %s", err)
|
||||
}
|
||||
|
||||
if jsRes.String() != "true" {
|
||||
t.Fatalf("Error executing: got %s but expected %s", jsRes, "true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessEnvValuesBrackets(t *testing.T) {
|
||||
vm := goja.New()
|
||||
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
for _, e := range os.Environ() {
|
||||
envKeyValue := strings.SplitN(e, "=", 2)
|
||||
jsExpr := fmt.Sprintf("process.env['%s']", envKeyValue[0])
|
||||
|
||||
jsRes, err := vm.RunString(jsExpr)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error executing %s: %s", jsExpr, err)
|
||||
}
|
||||
|
||||
if jsRes.String() != envKeyValue[1] {
|
||||
t.Fatalf("Error executing %s: got %s but expected %s", jsExpr, jsRes, envKeyValue[1])
|
||||
}
|
||||
}
|
||||
}
|
246
goja_nodejs/require/module.go
Normal file
246
goja_nodejs/require/module.go
Normal file
@ -0,0 +1,246 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
js "apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja/parser"
|
||||
)
|
||||
|
||||
type ModuleLoader func(*js.Runtime, *js.Object)
|
||||
|
||||
// SourceLoader represents a function that returns a file data at a given path.
|
||||
// The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory.
|
||||
// This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
|
||||
type SourceLoader func(path string) ([]byte, error)
|
||||
|
||||
var (
|
||||
InvalidModuleError = errors.New("Invalid module")
|
||||
IllegalModuleNameError = errors.New("Illegal module name")
|
||||
NoSuchBuiltInModuleError = errors.New("No such built-in module")
|
||||
ModuleFileDoesNotExistError = errors.New("module file does not exist")
|
||||
)
|
||||
|
||||
var native, builtin map[string]ModuleLoader
|
||||
|
||||
// Registry contains a cache of compiled modules which can be used by multiple Runtimes
|
||||
type Registry struct {
|
||||
sync.Mutex
|
||||
native map[string]ModuleLoader
|
||||
compiled map[string]*js.Program
|
||||
|
||||
srcLoader SourceLoader
|
||||
globalFolders []string
|
||||
}
|
||||
|
||||
type RequireModule struct {
|
||||
r *Registry
|
||||
runtime *js.Runtime
|
||||
modules map[string]*js.Object
|
||||
nodeModules map[string]*js.Object
|
||||
}
|
||||
|
||||
func NewRegistry(opts ...Option) *Registry {
|
||||
r := &Registry{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func NewRegistryWithLoader(srcLoader SourceLoader) *Registry {
|
||||
return NewRegistry(WithLoader(srcLoader))
|
||||
}
|
||||
|
||||
type Option func(*Registry)
|
||||
|
||||
// WithLoader sets a function which will be called by the require() function in order to get a source code for a
|
||||
// module at the given path. The same function will be used to get external source maps.
|
||||
// Note, this only affects the modules loaded by the require() function. If you need to use it as a source map
|
||||
// loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions()
|
||||
func WithLoader(srcLoader SourceLoader) Option {
|
||||
return func(r *Registry) {
|
||||
r.srcLoader = srcLoader
|
||||
}
|
||||
}
|
||||
|
||||
// WithGlobalFolders appends the given paths to the registry's list of
|
||||
// global folders to search if the requested module is not found
|
||||
// elsewhere. By default, a registry's global folders list is empty.
|
||||
// In the reference Node.js implementation, the default global folders
|
||||
// list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and
|
||||
// $PREFIX/lib/node, see
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders.
|
||||
func WithGlobalFolders(globalFolders ...string) Option {
|
||||
return func(r *Registry) {
|
||||
r.globalFolders = globalFolders
|
||||
}
|
||||
}
|
||||
|
||||
// Enable adds the require() function to the specified runtime.
|
||||
func (r *Registry) Enable(runtime *js.Runtime) *RequireModule {
|
||||
rrt := &RequireModule{
|
||||
r: r,
|
||||
runtime: runtime,
|
||||
modules: make(map[string]*js.Object),
|
||||
nodeModules: make(map[string]*js.Object),
|
||||
}
|
||||
|
||||
runtime.Set("require", rrt.require)
|
||||
return rrt
|
||||
}
|
||||
|
||||
func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.native == nil {
|
||||
r.native = make(map[string]ModuleLoader)
|
||||
}
|
||||
name = filepathClean(name)
|
||||
r.native[name] = loader
|
||||
}
|
||||
|
||||
// DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
|
||||
func DefaultSourceLoader(filename string) ([]byte, error) {
|
||||
fp := filepath.FromSlash(filename)
|
||||
f, err := os.Open(fp)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
err = ModuleFileDoesNotExistError
|
||||
} else if runtime.GOOS == "windows" {
|
||||
if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect.
|
||||
err = ModuleFileDoesNotExistError
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
// On some systems (e.g. plan9 and FreeBSD) it is possible to use the standard read() call on directories
|
||||
// which means we cannot rely on read() returning an error, we have to do stat() instead.
|
||||
if fi, err := f.Stat(); err == nil {
|
||||
if fi.IsDir() {
|
||||
return nil, ModuleFileDoesNotExistError
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(f)
|
||||
}
|
||||
|
||||
func (r *Registry) getSource(p string) ([]byte, error) {
|
||||
srcLoader := r.srcLoader
|
||||
if srcLoader == nil {
|
||||
srcLoader = DefaultSourceLoader
|
||||
}
|
||||
return srcLoader(p)
|
||||
}
|
||||
|
||||
func (r *Registry) getCompiledSource(p string) (*js.Program, error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
prg := r.compiled[p]
|
||||
if prg == nil {
|
||||
buf, err := r.getSource(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(buf)
|
||||
|
||||
if path.Ext(p) == ".json" {
|
||||
s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
|
||||
}
|
||||
|
||||
source := "(function(exports, require, module) {" + s + "\n})"
|
||||
parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prg, err = js.CompileAST(parsed, false)
|
||||
if err == nil {
|
||||
if r.compiled == nil {
|
||||
r.compiled = make(map[string]*js.Program)
|
||||
}
|
||||
r.compiled[p] = prg
|
||||
}
|
||||
return prg, err
|
||||
}
|
||||
return prg, nil
|
||||
}
|
||||
|
||||
func (r *RequireModule) require(call js.FunctionCall) js.Value {
|
||||
ret, err := r.Require(call.Argument(0).String())
|
||||
if err != nil {
|
||||
if _, ok := err.(*js.Exception); !ok {
|
||||
panic(r.runtime.NewGoError(err))
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func filepathClean(p string) string {
|
||||
return path.Clean(p)
|
||||
}
|
||||
|
||||
// Require can be used to import modules from Go source (similar to JS require() function).
|
||||
func (r *RequireModule) Require(p string) (ret js.Value, err error) {
|
||||
module, err := r.resolve(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret = module.Get("exports")
|
||||
return
|
||||
}
|
||||
|
||||
func Require(runtime *js.Runtime, name string) js.Value {
|
||||
if r, ok := js.AssertFunction(runtime.Get("require")); ok {
|
||||
mod, err := r(js.Undefined(), runtime.ToValue(name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mod
|
||||
}
|
||||
panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)"))
|
||||
}
|
||||
|
||||
// RegisterNativeModule registers a module that isn't loaded through a SourceLoader, but rather through
|
||||
// a provided ModuleLoader. Typically, this will be a module implemented in Go (although theoretically
|
||||
// it can be anything, depending on the ModuleLoader implementation).
|
||||
// Such modules take precedence over modules loaded through a SourceLoader, i.e. if a module name resolves as
|
||||
// native, the native module is loaded, and the SourceLoader is not consulted.
|
||||
// The binding is global and affects all instances of Registry.
|
||||
// It should be called from a package init() function as it may not be used concurrently with require() calls.
|
||||
// For registry-specific bindings see Registry.RegisterNativeModule.
|
||||
func RegisterNativeModule(name string, loader ModuleLoader) {
|
||||
if native == nil {
|
||||
native = make(map[string]ModuleLoader)
|
||||
}
|
||||
name = filepathClean(name)
|
||||
native[name] = loader
|
||||
}
|
||||
|
||||
// RegisterCoreModule registers a nodejs core module. If the name does not start with "node:", the module
|
||||
// will also be loadable as "node:<name>". Hence, for "builtin" modules (such as buffer, console, etc.)
|
||||
// the name should not include the "node:" prefix, but for prefix-only core modules (such as "node:test")
|
||||
// it should include the prefix.
|
||||
func RegisterCoreModule(name string, loader ModuleLoader) {
|
||||
if builtin == nil {
|
||||
builtin = make(map[string]ModuleLoader)
|
||||
}
|
||||
name = filepathClean(name)
|
||||
builtin[name] = loader
|
||||
}
|
534
goja_nodejs/require/module_test.go
Normal file
534
goja_nodejs/require/module_test.go
Normal file
@ -0,0 +1,534 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
js "apigo.cc/ai/ai/goja"
|
||||
)
|
||||
|
||||
func mapFileSystemSourceLoader(files map[string]string) SourceLoader {
|
||||
return func(path string) ([]byte, error) {
|
||||
s, ok := files[path]
|
||||
if !ok {
|
||||
return nil, ModuleFileDoesNotExistError
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireNativeModule(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
var m = require("test/m");
|
||||
m.test();
|
||||
`
|
||||
|
||||
vm := js.New()
|
||||
|
||||
registry := new(Registry)
|
||||
registry.Enable(vm)
|
||||
|
||||
RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) {
|
||||
o := module.Get("exports").(*js.Object)
|
||||
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||
return runtime.ToValue("passed")
|
||||
})
|
||||
})
|
||||
|
||||
v, err := vm.RunString(SCRIPT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !v.StrictEquals(vm.ToValue("passed")) {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterCoreModule(t *testing.T) {
|
||||
vm := js.New()
|
||||
|
||||
registry := new(Registry)
|
||||
registry.Enable(vm)
|
||||
|
||||
RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) {
|
||||
o := module.Get("exports").(*js.Object)
|
||||
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||
return runtime.ToValue("passed")
|
||||
})
|
||||
})
|
||||
|
||||
RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) {
|
||||
o := module.Get("exports").(*js.Object)
|
||||
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||
return runtime.ToValue("passed1")
|
||||
})
|
||||
})
|
||||
|
||||
RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) {
|
||||
o := module.Get("exports").(*js.Object)
|
||||
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||
return runtime.ToValue("test1 passed")
|
||||
})
|
||||
})
|
||||
|
||||
registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) {
|
||||
|
||||
})
|
||||
|
||||
_, err := vm.RunString(`
|
||||
const m1 = require("coremod");
|
||||
const m2 = require("node:coremod");
|
||||
if (m1 !== m2) {
|
||||
throw new Error("Modules are not equal");
|
||||
}
|
||||
if (m1.test() !== "passed") {
|
||||
throw new Error("m1.test() has failed");
|
||||
}
|
||||
|
||||
const m3 = require("node:coremod1");
|
||||
const m4 = require("coremod1");
|
||||
if (m3 !== m4) {
|
||||
throw new Error("Modules are not equal (1)");
|
||||
}
|
||||
if (m3.test() !== "passed1") {
|
||||
throw new Error("m3.test() has failed");
|
||||
}
|
||||
|
||||
try {
|
||||
require("node:bob");
|
||||
} catch (e) {
|
||||
if (!e.message.includes("No such built-in module")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
require("bob");
|
||||
|
||||
try {
|
||||
require("test1");
|
||||
throw new Error("Expected exception");
|
||||
} catch (e) {
|
||||
if (!e.message.includes("Invalid module")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (require("node:test1").test() !== "test1 passed") {
|
||||
throw new Error("test1.test() has failed");
|
||||
}
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireRegistryNativeModule(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
var log = require("test/log");
|
||||
log.print('passed');
|
||||
`
|
||||
|
||||
logWithOutput := func(w io.Writer, prefix string) ModuleLoader {
|
||||
return func(vm *js.Runtime, module *js.Object) {
|
||||
o := module.Get("exports").(*js.Object)
|
||||
o.Set("print", func(call js.FunctionCall) js.Value {
|
||||
fmt.Fprint(w, prefix, call.Argument(0).String())
|
||||
return js.Undefined()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
vm1 := js.New()
|
||||
buf1 := &bytes.Buffer{}
|
||||
|
||||
registry1 := new(Registry)
|
||||
registry1.Enable(vm1)
|
||||
|
||||
registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 "))
|
||||
|
||||
vm2 := js.New()
|
||||
buf2 := &bytes.Buffer{}
|
||||
|
||||
registry2 := new(Registry)
|
||||
registry2.Enable(vm2)
|
||||
|
||||
registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 "))
|
||||
|
||||
_, err := vm1.RunString(SCRIPT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := buf1.String()
|
||||
if s != "vm1 passed" {
|
||||
t.Fatalf("vm1: Unexpected result: %q", s)
|
||||
}
|
||||
|
||||
_, err = vm2.RunString(SCRIPT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s = buf2.String()
|
||||
if s != "vm2 passed" {
|
||||
t.Fatalf("vm2: Unexpected result: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequire(t *testing.T) {
|
||||
absPath, err := filepath.Abs("./testdata/m.js")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
isWindows := runtime.GOOS == "windows"
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
"./testdata/m.js",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"../require/testdata/m.js",
|
||||
true,
|
||||
},
|
||||
{
|
||||
absPath,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`.\testdata\m.js`,
|
||||
isWindows,
|
||||
},
|
||||
{
|
||||
`..\require\testdata\m.js`,
|
||||
isWindows,
|
||||
},
|
||||
}
|
||||
|
||||
const SCRIPT = `
|
||||
var m = require(testPath);
|
||||
m.test();
|
||||
`
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
vm := js.New()
|
||||
vm.Set("testPath", test.path)
|
||||
|
||||
registry := new(Registry)
|
||||
registry.Enable(vm)
|
||||
|
||||
v, err := vm.RunString(SCRIPT)
|
||||
|
||||
ok := err == nil
|
||||
|
||||
if ok != test.ok {
|
||||
t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !v.StrictEquals(vm.ToValue("passed")) {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceLoader(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
var m = require("m.js");
|
||||
m.test();
|
||||
`
|
||||
|
||||
const MODULE = `
|
||||
function test() {
|
||||
return "passed1";
|
||||
}
|
||||
|
||||
exports.test = test;
|
||||
`
|
||||
|
||||
vm := js.New()
|
||||
|
||||
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||||
if name == "m.js" {
|
||||
return []byte(MODULE), nil
|
||||
}
|
||||
return nil, errors.New("Module does not exist")
|
||||
}))
|
||||
registry.Enable(vm)
|
||||
|
||||
v, err := vm.RunString(SCRIPT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrictModule(t *testing.T) {
|
||||
const SCRIPT = `
|
||||
var m = require("m.js");
|
||||
m.test();
|
||||
`
|
||||
|
||||
const MODULE = `
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
var a = "passed1";
|
||||
eval("var a = 'not passed'");
|
||||
return a;
|
||||
}
|
||||
|
||||
exports.test = test;
|
||||
`
|
||||
|
||||
vm := js.New()
|
||||
|
||||
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||||
if name == "m.js" {
|
||||
return []byte(MODULE), nil
|
||||
}
|
||||
return nil, errors.New("Module does not exist")
|
||||
}))
|
||||
registry.Enable(vm)
|
||||
|
||||
v, err := vm.RunString(SCRIPT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) {
|
||||
vm := js.New()
|
||||
r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs)))
|
||||
r.Enable(vm)
|
||||
t.Logf("Require(%s)", fpath)
|
||||
ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return vm, ret, nil
|
||||
}
|
||||
|
||||
globalFolders := []string{
|
||||
"/usr/lib/node_modules",
|
||||
"/home/src/.node_modules",
|
||||
}
|
||||
|
||||
fs := map[string]string{
|
||||
"/home/src/app/app.js": `exports.name = "app"`,
|
||||
"/home/src/app2/app2.json": `{"name": "app2"}`,
|
||||
"/home/src/app3/index.js": `exports.name = "app3"`,
|
||||
"/home/src/app4/index.json": `{"name": "app4"}`,
|
||||
"/home/src/app5/package.json": `{"main": "app5.js"}`,
|
||||
"/home/src/app5/app5.js": `exports.name = "app5"`,
|
||||
"/home/src/app6/package.json": `{"main": "."}`,
|
||||
"/home/src/app6/index.js": `exports.name = "app6"`,
|
||||
"/home/src/app7/package.json": `{"main": "./a/b/c/file.js"}`,
|
||||
"/home/src/app7/a/b/c/file.js": `exports.name = "app7"`,
|
||||
"/usr/lib/node_modules/app8": `exports.name = "app8"`,
|
||||
"/home/src/app9/app9.js": `exports.name = require('./a/file.js').name`,
|
||||
"/home/src/app9/a/file.js": `exports.name = require('./b/file.js').name`,
|
||||
"/home/src/app9/a/b/file.js": `exports.name = require('./c/file.js').name`,
|
||||
"/home/src/app9/a/b/c/file.js": `exports.name = "app9"`,
|
||||
"/home/src/.node_modules/app10": `exports.name = "app10"`,
|
||||
"/home/src/app11/app11.js": `exports.name = require('d/file.js').name`,
|
||||
"/home/src/app11/a/b/c/app11.js": `exports.name = require('d/file.js').name`,
|
||||
"/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`,
|
||||
"/app12.js": `exports.name = require('a/file.js').name`,
|
||||
"/node_modules/a/file.js": `exports.name = "app12"`,
|
||||
"/app13/app13.js": `exports.name = require('b/file.js').name`,
|
||||
"/node_modules/b/file.js": `exports.name = "app13"`,
|
||||
"node_modules/app14/index.js": `exports.name = "app14"`,
|
||||
"../node_modules/app15/index.js": `exports.name = "app15"`,
|
||||
}
|
||||
|
||||
for i, tc := range []struct {
|
||||
src string
|
||||
path string
|
||||
ok bool
|
||||
field string
|
||||
value string
|
||||
}{
|
||||
{"/home/src", "./app/app", true, "name", "app"},
|
||||
{"/home/src", "./app/app.js", true, "name", "app"},
|
||||
{"/home/src", "./app/bad.js", false, "", ""},
|
||||
{"/home/src", "./app2/app2", true, "name", "app2"},
|
||||
{"/home/src", "./app2/app2.json", true, "name", "app2"},
|
||||
{"/home/src", "./app/bad.json", false, "", ""},
|
||||
{"/home/src", "./app3", true, "name", "app3"},
|
||||
{"/home/src", "./appbad", false, "", ""},
|
||||
{"/home/src", "./app4", true, "name", "app4"},
|
||||
{"/home/src", "./appbad", false, "", ""},
|
||||
{"/home/src", "./app5", true, "name", "app5"},
|
||||
{"/home/src", "./app6", true, "name", "app6"},
|
||||
{"/home/src", "./app7", true, "name", "app7"},
|
||||
{"/home/src", "app8", true, "name", "app8"},
|
||||
{"/home/src", "./app9/app9", true, "name", "app9"},
|
||||
{"/home/src", "app10", true, "name", "app10"},
|
||||
{"/home/src", "./app11/app11.js", true, "name", "app11"},
|
||||
{"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"},
|
||||
{"/", "./app12", true, "name", "app12"},
|
||||
{"/", "./app13/app13", true, "name", "app13"},
|
||||
{".", "app14", true, "name", "app14"},
|
||||
{"..", "nonexistent", false, "", ""},
|
||||
} {
|
||||
vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs)
|
||||
if err != nil {
|
||||
if tc.ok {
|
||||
t.Errorf("%d: require() failed: %v", i, err)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
if !tc.ok {
|
||||
t.Errorf("%d: expected to fail, but did not", i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
f := mod.ToObject(vm).Get(tc.field)
|
||||
if f == nil {
|
||||
t.Errorf("%v: field %q not found", i, tc.field)
|
||||
continue
|
||||
}
|
||||
value := f.String()
|
||||
if value != tc.value {
|
||||
t.Errorf("%v: got %q expected %q", i, value, tc.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireCycle(t *testing.T) {
|
||||
vm := js.New()
|
||||
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||||
"a.js": `var b = require('./b.js'); exports.done = true;`,
|
||||
"b.js": `var a = require('./a.js'); exports.done = true;`,
|
||||
})))
|
||||
r.Enable(vm)
|
||||
res, err := vm.RunString(`
|
||||
var a = require('./a.js');
|
||||
var b = require('./b.js');
|
||||
a.done && b.done;
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := res.Export(); v != true {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorPropagation(t *testing.T) {
|
||||
vm := js.New()
|
||||
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||||
"m.js": `throw 'test passed';`,
|
||||
})))
|
||||
rr := r.Enable(vm)
|
||||
_, err := rr.Require("./m")
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
if ex, ok := err.(*js.Exception); ok {
|
||||
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||
t.Fatalf("Unexpected Exception: %v", ex)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceMapLoader(t *testing.T) {
|
||||
vm := js.New()
|
||||
r := NewRegistry(WithLoader(func(p string) ([]byte, error) {
|
||||
switch p {
|
||||
case "dir/m.js":
|
||||
return []byte(`throw 'test passed';
|
||||
//# sourceMappingURL=m.js.map`), nil
|
||||
case "dir/m.js.map":
|
||||
return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"}
|
||||
`), nil
|
||||
}
|
||||
return nil, ModuleFileDoesNotExistError
|
||||
}))
|
||||
|
||||
rr := r.Enable(vm)
|
||||
_, err := rr.Require("./dir/m")
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
if ex, ok := err.(*js.Exception); ok {
|
||||
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||
t.Fatalf("Unexpected Exception: %v", ex)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testsetup() (string, func(), error) {
|
||||
name, err := os.MkdirTemp("", "goja-nodejs-require-test")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return name, func() {
|
||||
os.RemoveAll(name)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestDefaultModuleLoader(t *testing.T) {
|
||||
workdir, teardown, err := testsetup()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer teardown()
|
||||
|
||||
err = os.Chdir(workdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.Mkdir("module", 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
vm := js.New()
|
||||
r := NewRegistry()
|
||||
rr := r.Enable(vm)
|
||||
_, err = rr.Require("./module")
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
if ex, ok := err.(*js.Exception); ok {
|
||||
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||
t.Fatalf("Unexpected Exception: %v", ex)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
276
goja_nodejs/require/resolve.go
Normal file
276
goja_nodejs/require/resolve.go
Normal file
@ -0,0 +1,276 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
js "apigo.cc/ai/ai/goja"
|
||||
)
|
||||
|
||||
const NodePrefix = "node:"
|
||||
|
||||
// NodeJS module search algorithm described by
|
||||
// https://nodejs.org/api/modules.html#modules_all_together
|
||||
func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
|
||||
origPath, modpath := modpath, filepathClean(modpath)
|
||||
if modpath == "" {
|
||||
return nil, IllegalModuleNameError
|
||||
}
|
||||
|
||||
var start string
|
||||
err = nil
|
||||
if path.IsAbs(origPath) {
|
||||
start = "/"
|
||||
} else {
|
||||
start = r.getCurrentModulePath()
|
||||
}
|
||||
|
||||
p := path.Join(start, modpath)
|
||||
if isFileOrDirectoryPath(origPath) {
|
||||
if module = r.modules[p]; module != nil {
|
||||
return
|
||||
}
|
||||
module, err = r.loadAsFileOrDirectory(p)
|
||||
if err == nil && module != nil {
|
||||
r.modules[p] = module
|
||||
}
|
||||
} else {
|
||||
module, err = r.loadNative(origPath)
|
||||
if err == nil {
|
||||
return
|
||||
} else {
|
||||
if err == InvalidModuleError {
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if module = r.nodeModules[p]; module != nil {
|
||||
return
|
||||
}
|
||||
module, err = r.loadNodeModules(modpath, start)
|
||||
if err == nil && module != nil {
|
||||
r.nodeModules[p] = module
|
||||
}
|
||||
}
|
||||
|
||||
if module == nil && err == nil {
|
||||
err = InvalidModuleError
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNative(path string) (*js.Object, error) {
|
||||
module := r.modules[path]
|
||||
if module != nil {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
var ldr ModuleLoader
|
||||
if ldr = r.r.native[path]; ldr == nil {
|
||||
ldr = native[path]
|
||||
}
|
||||
|
||||
var isBuiltIn, withPrefix bool
|
||||
if ldr == nil {
|
||||
ldr = builtin[path]
|
||||
if ldr == nil && strings.HasPrefix(path, NodePrefix) {
|
||||
ldr = builtin[path[len(NodePrefix):]]
|
||||
if ldr == nil {
|
||||
return nil, NoSuchBuiltInModuleError
|
||||
}
|
||||
withPrefix = true
|
||||
}
|
||||
isBuiltIn = true
|
||||
}
|
||||
|
||||
if ldr != nil {
|
||||
module = r.createModuleObject()
|
||||
r.modules[path] = module
|
||||
if isBuiltIn {
|
||||
if withPrefix {
|
||||
r.modules[path[len(NodePrefix):]] = module
|
||||
} else {
|
||||
if !strings.HasPrefix(path, NodePrefix) {
|
||||
r.modules[NodePrefix+path] = module
|
||||
}
|
||||
}
|
||||
}
|
||||
ldr(r.runtime, module)
|
||||
return module, nil
|
||||
}
|
||||
|
||||
return nil, InvalidModuleError
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, err error) {
|
||||
if module, err = r.loadAsFile(path); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return r.loadAsDirectory(path)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
||||
if module, err = r.loadModule(path); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p := path + ".js"
|
||||
if module, err = r.loadModule(p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = path + ".json"
|
||||
return r.loadModule(p)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) {
|
||||
p := path.Join(modpath, "index.js")
|
||||
if module, err = r.loadModule(p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = path.Join(modpath, "index.json")
|
||||
return r.loadModule(p)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) {
|
||||
p := path.Join(modpath, "package.json")
|
||||
buf, err := r.r.getSource(p)
|
||||
if err != nil {
|
||||
return r.loadIndex(modpath)
|
||||
}
|
||||
var pkg struct {
|
||||
Main string
|
||||
}
|
||||
err = json.Unmarshal(buf, &pkg)
|
||||
if err != nil || len(pkg.Main) == 0 {
|
||||
return r.loadIndex(modpath)
|
||||
}
|
||||
|
||||
m := path.Join(modpath, pkg.Main)
|
||||
if module, err = r.loadAsFile(m); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return r.loadIndex(m)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) {
|
||||
return r.loadAsFileOrDirectory(path.Join(start, modpath))
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) {
|
||||
for _, dir := range r.r.globalFolders {
|
||||
if module, err = r.loadNodeModule(modpath, dir); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
var p string
|
||||
if path.Base(start) != "node_modules" {
|
||||
p = path.Join(start, "node_modules")
|
||||
} else {
|
||||
p = start
|
||||
}
|
||||
if module, err = r.loadNodeModule(modpath, p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
if start == ".." { // Dir('..') is '.'
|
||||
break
|
||||
}
|
||||
parent := path.Dir(start)
|
||||
if parent == start {
|
||||
break
|
||||
}
|
||||
start = parent
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RequireModule) getCurrentModulePath() string {
|
||||
var buf [2]js.StackFrame
|
||||
frames := r.runtime.CaptureCallStack(2, buf[:0])
|
||||
if len(frames) < 2 {
|
||||
return "."
|
||||
}
|
||||
return path.Dir(frames[1].SrcName())
|
||||
}
|
||||
|
||||
func (r *RequireModule) createModuleObject() *js.Object {
|
||||
module := r.runtime.NewObject()
|
||||
module.Set("exports", r.runtime.NewObject())
|
||||
return module
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadModule(path string) (*js.Object, error) {
|
||||
module := r.modules[path]
|
||||
if module == nil {
|
||||
module = r.createModuleObject()
|
||||
r.modules[path] = module
|
||||
err := r.loadModuleFile(path, module)
|
||||
if err != nil {
|
||||
module = nil
|
||||
delete(r.modules, path)
|
||||
if errors.Is(err, ModuleFileDoesNotExistError) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return module, err
|
||||
}
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error {
|
||||
|
||||
prg, err := r.r.getCompiledSource(path)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := r.runtime.RunProgram(prg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if call, ok := js.AssertFunction(f); ok {
|
||||
jsExports := jsModule.Get("exports")
|
||||
jsRequire := r.runtime.Get("require")
|
||||
|
||||
// Run the module source, with "jsExports" as "this",
|
||||
// "jsExports" as the "exports" variable, "jsRequire"
|
||||
// as the "require" variable and "jsModule" as the
|
||||
// "module" variable (Nodejs capable).
|
||||
_, err = call(jsExports, jsExports, jsRequire, jsModule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return InvalidModuleError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFileOrDirectoryPath(path string) bool {
|
||||
result := path == "." || path == ".." ||
|
||||
strings.HasPrefix(path, "/") ||
|
||||
strings.HasPrefix(path, "./") ||
|
||||
strings.HasPrefix(path, "../")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
result = result ||
|
||||
strings.HasPrefix(path, `.\`) ||
|
||||
strings.HasPrefix(path, `..\`) ||
|
||||
filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
7
goja_nodejs/require/testdata/m.js
vendored
Normal file
7
goja_nodejs/require/testdata/m.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
function test() {
|
||||
return "passed";
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
test: test
|
||||
}
|
1
goja_nodejs/staticcheck.conf
Normal file
1
goja_nodejs/staticcheck.conf
Normal file
@ -0,0 +1 @@
|
||||
checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1006", "-ST1012", "-ST1021", "-ST1020", "-ST1008"]
|
134
goja_nodejs/url/escape.go
Normal file
134
goja_nodejs/url/escape.go
Normal file
@ -0,0 +1,134 @@
|
||||
package url
|
||||
|
||||
import "strings"
|
||||
|
||||
var tblEscapeURLQuery = [128]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
}
|
||||
|
||||
var tblEscapeURLQueryParam = [128]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
// The code below is mostly borrowed from the standard Go url package
|
||||
|
||||
const upperhex = "0123456789ABCDEF"
|
||||
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func escape(s string, table *[128]byte, spaceToPlus bool) string {
|
||||
spaceCount, hexCount := 0, 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c > 127 || table[c] == 0 {
|
||||
if c == ' ' && spaceToPlus {
|
||||
spaceCount++
|
||||
} else {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if spaceCount == 0 && hexCount == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
hexBuf := [3]byte{'%', 0, 0}
|
||||
|
||||
sb.Grow(len(s) + 2*hexCount)
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case c == ' ' && spaceToPlus:
|
||||
sb.WriteByte('+')
|
||||
case c > 127 || table[c] == 0:
|
||||
hexBuf[1] = upperhex[c>>4]
|
||||
hexBuf[2] = upperhex[c&15]
|
||||
sb.Write(hexBuf[:])
|
||||
default:
|
||||
sb.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func unescapeSearchParam(s string) string {
|
||||
n := 0
|
||||
hasPlus := false
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
n++
|
||||
i += 3
|
||||
case '+':
|
||||
hasPlus = true
|
||||
i++
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 && !hasPlus {
|
||||
return s
|
||||
}
|
||||
|
||||
var t strings.Builder
|
||||
t.Grow(len(s) - 2*n)
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||
t.WriteByte('%')
|
||||
} else {
|
||||
t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
|
||||
i += 2
|
||||
}
|
||||
case '+':
|
||||
t.WriteByte(' ')
|
||||
default:
|
||||
t.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
return t.String()
|
||||
}
|
36
goja_nodejs/url/module.go
Normal file
36
goja_nodejs/url/module.go
Normal file
@ -0,0 +1,36 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
const ModuleName = "url"
|
||||
|
||||
type urlModule struct {
|
||||
r *goja.Runtime
|
||||
|
||||
URLSearchParamsPrototype *goja.Object
|
||||
URLSearchParamsIteratorPrototype *goja.Object
|
||||
}
|
||||
|
||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||
exports := module.Get("exports").(*goja.Object)
|
||||
m := &urlModule{
|
||||
r: runtime,
|
||||
}
|
||||
exports.Set("URL", m.createURLConstructor())
|
||||
exports.Set("URLSearchParams", m.createURLSearchParamsConstructor())
|
||||
exports.Set("domainToASCII", m.domainToASCII)
|
||||
exports.Set("domainToUnicode", m.domainToUnicode)
|
||||
}
|
||||
|
||||
func Enable(runtime *goja.Runtime) {
|
||||
m := require.Require(runtime, ModuleName).ToObject(runtime)
|
||||
runtime.Set("URL", m.Get("URL"))
|
||||
runtime.Set("URLSearchParams", m.Get("URLSearchParams"))
|
||||
}
|
||||
|
||||
func init() {
|
||||
require.RegisterCoreModule(ModuleName, Require)
|
||||
}
|
148
goja_nodejs/url/nodeurl.go
Normal file
148
goja_nodejs/url/nodeurl.go
Normal file
@ -0,0 +1,148 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type searchParam struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
func (sp *searchParam) Encode() string {
|
||||
return sp.string(true)
|
||||
}
|
||||
|
||||
func escapeSearchParam(s string) string {
|
||||
return escape(s, &tblEscapeURLQueryParam, true)
|
||||
}
|
||||
|
||||
func (sp *searchParam) string(encode bool) string {
|
||||
if encode {
|
||||
return escapeSearchParam(sp.name) + "=" + escapeSearchParam(sp.value)
|
||||
} else {
|
||||
return sp.name + "=" + sp.value
|
||||
}
|
||||
}
|
||||
|
||||
type searchParams []searchParam
|
||||
|
||||
func (s searchParams) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s searchParams) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s searchParams) Less(i, j int) bool {
|
||||
return strings.Compare(s[i].name, s[j].name) < 0
|
||||
}
|
||||
|
||||
func (s searchParams) Encode() string {
|
||||
var sb strings.Builder
|
||||
for i, v := range s {
|
||||
if i > 0 {
|
||||
sb.WriteByte('&')
|
||||
}
|
||||
sb.WriteString(v.Encode())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (s searchParams) String() string {
|
||||
var sb strings.Builder
|
||||
for i, v := range s {
|
||||
if i > 0 {
|
||||
sb.WriteByte('&')
|
||||
}
|
||||
sb.WriteString(v.string(false))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type nodeURL struct {
|
||||
url *url.URL
|
||||
searchParams searchParams
|
||||
}
|
||||
|
||||
type urlSearchParams nodeURL
|
||||
|
||||
// This methods ensures that the url.URL has the proper RawQuery based on the searchParam
|
||||
// structs. If a change is made to the searchParams we need to keep them in sync.
|
||||
func (nu *nodeURL) syncSearchParams() {
|
||||
if nu.rawQueryUpdateNeeded() {
|
||||
nu.url.RawQuery = nu.searchParams.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
func (nu *nodeURL) rawQueryUpdateNeeded() bool {
|
||||
return len(nu.searchParams) > 0 && nu.url.RawQuery == ""
|
||||
}
|
||||
|
||||
func (nu *nodeURL) String() string {
|
||||
return nu.url.String()
|
||||
}
|
||||
|
||||
func (sp *urlSearchParams) hasName(name string) bool {
|
||||
for _, v := range sp.searchParams {
|
||||
if v.name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sp *urlSearchParams) hasValue(name, value string) bool {
|
||||
for _, v := range sp.searchParams {
|
||||
if v.name == name && v.value == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sp *urlSearchParams) getValues(name string) []string {
|
||||
vals := make([]string, 0, len(sp.searchParams))
|
||||
for _, v := range sp.searchParams {
|
||||
if v.name == name {
|
||||
vals = append(vals, v.value)
|
||||
}
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (sp *urlSearchParams) getFirstValue(name string) (string, bool) {
|
||||
for _, v := range sp.searchParams {
|
||||
if v.name == name {
|
||||
return v.value, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func parseSearchQuery(query string) (ret searchParams) {
|
||||
if query == "" {
|
||||
return
|
||||
}
|
||||
|
||||
query = strings.TrimPrefix(query, "?")
|
||||
|
||||
for _, v := range strings.Split(query, "&") {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
pair := strings.SplitN(v, "=", 2)
|
||||
l := len(pair)
|
||||
if l == 1 {
|
||||
ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: ""})
|
||||
} else if l == 2 {
|
||||
ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: unescapeSearchParam(pair[1])})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
385
goja_nodejs/url/testdata/url_search_params.js
vendored
Normal file
385
goja_nodejs/url/testdata/url_search_params.js
vendored
Normal file
@ -0,0 +1,385 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("../../assert.js");
|
||||
|
||||
let params;
|
||||
|
||||
function testCtor(value, expected) {
|
||||
assert.sameValue(new URLSearchParams(value).toString(), expected);
|
||||
}
|
||||
|
||||
testCtor("user=abc&query=xyz", "user=abc&query=xyz");
|
||||
testCtor("?user=abc&query=xyz", "user=abc&query=xyz");
|
||||
|
||||
testCtor(
|
||||
{
|
||||
num: 1,
|
||||
user: "abc",
|
||||
query: ["first", "second"],
|
||||
obj: { prop: "value" },
|
||||
b: true,
|
||||
},
|
||||
"num=1&user=abc&query=first%2Csecond&obj=%5Bobject+Object%5D&b=true"
|
||||
);
|
||||
|
||||
const map = new Map();
|
||||
map.set("user", "abc");
|
||||
map.set("query", "xyz");
|
||||
testCtor(map, "user=abc&query=xyz");
|
||||
|
||||
testCtor(
|
||||
[
|
||||
["user", "abc"],
|
||||
["query", "first"],
|
||||
["query", "second"],
|
||||
],
|
||||
"user=abc&query=first&query=second"
|
||||
);
|
||||
|
||||
// Each key-value pair must have exactly two elements
|
||||
assert.throwsNodeError(() => new URLSearchParams([["single_value"]]), TypeError, "ERR_INVALID_TUPLE");
|
||||
assert.throwsNodeError(() => new URLSearchParams([["too", "many", "values"]]), TypeError, "ERR_INVALID_TUPLE");
|
||||
|
||||
params = new URLSearchParams("a=b&cc=d");
|
||||
params.forEach((value, name, searchParams) => {
|
||||
if (name === "a") {
|
||||
assert.sameValue(value, "b");
|
||||
}
|
||||
if (name === "cc") {
|
||||
assert.sameValue(value, "d");
|
||||
}
|
||||
assert.sameValue(searchParams, params);
|
||||
});
|
||||
|
||||
params.forEach((value, name, searchParams) => {
|
||||
if (name === "a") {
|
||||
assert.sameValue(value, "b");
|
||||
searchParams.set("cc", "d1");
|
||||
}
|
||||
if (name === "cc") {
|
||||
assert.sameValue(value, "d1");
|
||||
}
|
||||
assert.sameValue(searchParams, params);
|
||||
});
|
||||
|
||||
assert.throwsNodeError(() => params.forEach(123), TypeError, "ERR_INVALID_ARG_TYPE");
|
||||
|
||||
assert.throwsNodeError(() => params.forEach.call(1, 2), TypeError, "ERR_INVALID_THIS");
|
||||
|
||||
params = new URLSearchParams("a=1=2&b=3");
|
||||
assert.sameValue(params.size, 2);
|
||||
assert.sameValue(params.get("a"), "1=2");
|
||||
assert.sameValue(params.get("b"), "3");
|
||||
|
||||
params = new URLSearchParams("&");
|
||||
assert.sameValue(params.size, 0);
|
||||
|
||||
params = new URLSearchParams("& ");
|
||||
assert.sameValue(params.size, 1);
|
||||
assert.sameValue(params.get(" "), "");
|
||||
|
||||
params = new URLSearchParams(" &");
|
||||
assert.sameValue(params.size, 1);
|
||||
assert.sameValue(params.get(" "), "");
|
||||
|
||||
params = new URLSearchParams("=");
|
||||
assert.sameValue(params.size, 1);
|
||||
assert.sameValue(params.get(""), "");
|
||||
|
||||
params = new URLSearchParams("&=2");
|
||||
assert.sameValue(params.size, 1);
|
||||
assert.sameValue(params.get(""), "2");
|
||||
|
||||
params = new URLSearchParams("?user=abc");
|
||||
assert.throwsNodeError(() => params.append(), TypeError, "ERR_MISSING_ARGS");
|
||||
params.append("query", "first");
|
||||
assert.sameValue(params.toString(), "user=abc&query=first");
|
||||
|
||||
params = new URLSearchParams("first=one&second=two&third=three");
|
||||
assert.throwsNodeError(() => params.delete(), TypeError, "ERR_MISSING_ARGS");
|
||||
params.delete("second", "fake-value");
|
||||
assert.sameValue(params.toString(), "first=one&second=two&third=three");
|
||||
params.delete("third", "three");
|
||||
assert.sameValue(params.toString(), "first=one&second=two");
|
||||
params.delete("second");
|
||||
assert.sameValue(params.toString(), "first=one");
|
||||
|
||||
params = new URLSearchParams("user=abc&query=xyz");
|
||||
assert.throwsNodeError(() => params.get(), TypeError, "ERR_MISSING_ARGS");
|
||||
assert.sameValue(params.get("user"), "abc");
|
||||
assert.sameValue(params.get("non-existant"), null);
|
||||
|
||||
params = new URLSearchParams("query=first&query=second");
|
||||
assert.throwsNodeError(() => params.getAll(), TypeError, "ERR_MISSING_ARGS");
|
||||
const all = params.getAll("query");
|
||||
assert.sameValue(all.includes("first"), true);
|
||||
assert.sameValue(all.includes("second"), true);
|
||||
assert.sameValue(all.length, 2);
|
||||
const getAllUndefined = params.getAll(undefined);
|
||||
assert.sameValue(getAllUndefined.length, 0);
|
||||
const getAllNonExistant = params.getAll("does_not_exists");
|
||||
assert.sameValue(getAllNonExistant.length, 0);
|
||||
|
||||
params = new URLSearchParams("user=abc&query=xyz");
|
||||
assert.throwsNodeError(() => params.has(), TypeError, "ERR_MISSING_ARGS");
|
||||
assert.sameValue(params.has(undefined), false);
|
||||
assert.sameValue(params.has("user"), true);
|
||||
assert.sameValue(params.has("user", "abc"), true);
|
||||
assert.sameValue(params.has("user", "abc", "extra-param"), true);
|
||||
assert.sameValue(params.has("user", "efg"), false);
|
||||
assert.sameValue(params.has("user", undefined), true);
|
||||
|
||||
params = new URLSearchParams();
|
||||
params.append("foo", "bar");
|
||||
params.append("foo", "baz");
|
||||
params.append("abc", "def");
|
||||
assert.sameValue(params.toString(), "foo=bar&foo=baz&abc=def");
|
||||
params.set("foo", "def");
|
||||
params.set("xyz", "opq");
|
||||
assert.sameValue(params.toString(), "foo=def&abc=def&xyz=opq");
|
||||
|
||||
params = new URLSearchParams("query=first&query=second&user=abc&double=first,second");
|
||||
const URLSearchIteratorPrototype = params.entries().__proto__;
|
||||
assert.sameValue(typeof URLSearchIteratorPrototype, "object");
|
||||
|
||||
assert.sameValue(params[Symbol.iterator], params.entries);
|
||||
|
||||
{
|
||||
const entries = params.entries();
|
||||
assert.sameValue(entries.toString(), "[object URLSearchParams Iterator]");
|
||||
assert.sameValue(entries.__proto__, URLSearchIteratorPrototype);
|
||||
|
||||
let item = entries.next();
|
||||
assert.sameValue(item.value.toString(), ["query", "first"].toString());
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = entries.next();
|
||||
assert.sameValue(item.value.toString(), ["query", "second"].toString());
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = entries.next();
|
||||
assert.sameValue(item.value.toString(), ["user", "abc"].toString());
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = entries.next();
|
||||
assert.sameValue(item.value.toString(), ["double", "first,second"].toString());
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = entries.next();
|
||||
assert.sameValue(item.value, undefined);
|
||||
assert.sameValue(item.done, true);
|
||||
}
|
||||
|
||||
params = new URLSearchParams("query=first&query=second&user=abc");
|
||||
{
|
||||
const keys = params.keys();
|
||||
assert.sameValue(keys.__proto__, URLSearchIteratorPrototype);
|
||||
|
||||
let item = keys.next();
|
||||
assert.sameValue(item.value, "query");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = keys.next();
|
||||
assert.sameValue(item.value, "query");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = keys.next();
|
||||
assert.sameValue(item.value, "user");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = keys.next();
|
||||
assert.sameValue(item.value, undefined);
|
||||
assert.sameValue(item.done, true);
|
||||
}
|
||||
|
||||
params = new URLSearchParams("query=first&query=second&user=abc");
|
||||
{
|
||||
const values = params.values();
|
||||
assert.sameValue(values.__proto__, URLSearchIteratorPrototype);
|
||||
|
||||
let item = values.next();
|
||||
assert.sameValue(item.value, "first");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = values.next();
|
||||
assert.sameValue(item.value, "second");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = values.next();
|
||||
assert.sameValue(item.value, "abc");
|
||||
assert.sameValue(item.done, false);
|
||||
|
||||
item = values.next();
|
||||
assert.sameValue(item.value, undefined);
|
||||
assert.sameValue(item.done, true);
|
||||
}
|
||||
|
||||
|
||||
params = new URLSearchParams("query[]=abc&type=search&query[]=123");
|
||||
params.sort();
|
||||
assert.sameValue(params.toString(), "query%5B%5D=abc&query%5B%5D=123&type=search");
|
||||
|
||||
params = new URLSearchParams("query=first&query=second&user=abc");
|
||||
assert.sameValue(params.size, 3);
|
||||
|
||||
params = new URLSearchParams("%");
|
||||
assert.sameValue(params.has("%"), true);
|
||||
assert.sameValue(params.toString(), "%25=");
|
||||
|
||||
{
|
||||
const params = new URLSearchParams("");
|
||||
assert.sameValue(params.size, 0);
|
||||
assert.sameValue(params.toString(), "");
|
||||
assert.sameValue(params.get(undefined), null);
|
||||
params.set(undefined, true);
|
||||
assert.sameValue(params.has(undefined), true);
|
||||
assert.sameValue(params.has("undefined"), true);
|
||||
assert.sameValue(params.get("undefined"), "true");
|
||||
assert.sameValue(params.get(undefined), "true");
|
||||
assert.sameValue(params.getAll(undefined).toString(), ["true"].toString());
|
||||
params.delete(undefined);
|
||||
assert.sameValue(params.has(undefined), false);
|
||||
assert.sameValue(params.has("undefined"), false);
|
||||
|
||||
assert.sameValue(params.has(null), false);
|
||||
params.set(null, "nullval");
|
||||
assert.sameValue(params.has(null), true);
|
||||
assert.sameValue(params.has("null"), true);
|
||||
assert.sameValue(params.get(null), "nullval");
|
||||
assert.sameValue(params.get("null"), "nullval");
|
||||
params.delete(null);
|
||||
assert.sameValue(params.has(null), false);
|
||||
assert.sameValue(params.has("null"), false);
|
||||
}
|
||||
|
||||
function* functionGeneratorExample() {
|
||||
yield ["user", "abc"];
|
||||
yield ["query", "first"];
|
||||
yield ["query", "second"];
|
||||
}
|
||||
|
||||
params = new URLSearchParams(functionGeneratorExample());
|
||||
assert.sameValue(params.toString(), "user=abc&query=first&query=second");
|
||||
|
||||
assert.sameValue(params.__proto__.constructor, URLSearchParams);
|
||||
assert.sameValue(params instanceof URLSearchParams, true);
|
||||
|
||||
{
|
||||
const params = new URLSearchParams("1=2&1=3");
|
||||
assert.sameValue(params.get(1), "2");
|
||||
assert.sameValue(params.getAll(1).toString(), ["2", "3"].toString());
|
||||
assert.sameValue(params.getAll("x").toString(), [].toString());
|
||||
}
|
||||
|
||||
// Sync
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
const params = url.searchParams;
|
||||
assert.sameValue(params.size, 0);
|
||||
url.search = "a=1";
|
||||
assert.sameValue(params.size, 1);
|
||||
assert.sameValue(params.get("a"), "1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/?a=1");
|
||||
const params = url.searchParams;
|
||||
assert.sameValue(params.size, 1);
|
||||
url.search = "";
|
||||
assert.sameValue(params.size, 0);
|
||||
url.search = "b=2";
|
||||
assert.sameValue(params.size, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
const params = url.searchParams;
|
||||
params.append("a", "1");
|
||||
assert.sameValue(url.toString(), "https://test.com/?a=1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
url.searchParams.append("a", "1");
|
||||
url.searchParams.append("b", "1");
|
||||
assert.sameValue(url.toString(), "https://test.com/?a=1&b=1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
const params = url.searchParams;
|
||||
url.searchParams.append("a", "1");
|
||||
assert.sameValue(url.search, "?a=1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/?a=1");
|
||||
const params = url.searchParams;
|
||||
params.append("a", "2");
|
||||
assert.sameValue(url.search, "?a=1&a=2");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
const params = url.searchParams;
|
||||
params.set("a", "1");
|
||||
assert.sameValue(url.search, "?a=1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/");
|
||||
url.searchParams.set("a", "1");
|
||||
url.searchParams.set("b", "1");
|
||||
assert.sameValue(url.toString(), "https://test.com/?a=1&b=1");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/?a=1&b=2");
|
||||
const params = url.searchParams;
|
||||
params.delete("a");
|
||||
assert.sameValue(url.search, "?b=2");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/?b=2&a=1");
|
||||
const params = url.searchParams;
|
||||
params.sort();
|
||||
assert.sameValue(url.search, "?a=1&b=2");
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL("https://test.com/?a=1");
|
||||
const params = url.searchParams;
|
||||
params.delete("a");
|
||||
assert.sameValue(url.search, "");
|
||||
|
||||
params.set("a", 2);
|
||||
assert.sameValue(url.search, "?a=2");
|
||||
}
|
||||
|
||||
// FAILING: no custom properties on wrapped Go structs
|
||||
/*
|
||||
{
|
||||
const params = new URLSearchParams("");
|
||||
assert.sameValue(Object.isExtensible(params), true);
|
||||
assert.sameValue(Reflect.defineProperty(params, "customField", {value: 42, configurable: true}), true);
|
||||
assert.sameValue(params.customField, 42);
|
||||
const desc = Reflect.getOwnPropertyDescriptor(params, "customField");
|
||||
assert.sameValue(desc.value, 42);
|
||||
assert.sameValue(desc.writable, false);
|
||||
assert.sameValue(desc.enumerable, false);
|
||||
assert.sameValue(desc.configurable, true);
|
||||
}
|
||||
*/
|
||||
|
||||
// Escape
|
||||
{
|
||||
const myURL = new URL('https://example.org/abc?fo~o=~ba r%z');
|
||||
|
||||
assert.sameValue(myURL.search, "?fo~o=~ba%20r%z");
|
||||
|
||||
// Modify the URL via searchParams...
|
||||
myURL.searchParams.sort();
|
||||
|
||||
assert.sameValue(myURL.search, "?fo%7Eo=%7Eba+r%25z");
|
||||
}
|
229
goja_nodejs/url/testdata/url_test.js
vendored
Normal file
229
goja_nodejs/url/testdata/url_test.js
vendored
Normal file
@ -0,0 +1,229 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("../../assert.js");
|
||||
|
||||
function testURLCtor(str, expected) {
|
||||
assert.sameValue(new URL(str).toString(), expected);
|
||||
}
|
||||
|
||||
function testURLCtorBase(ref, base, expected, message) {
|
||||
assert.sameValue(new URL(ref, base).toString(), expected, message);
|
||||
}
|
||||
|
||||
testURLCtorBase("https://example.org/", undefined, "https://example.org/");
|
||||
testURLCtorBase("/foo", "https://example.org/", "https://example.org/foo");
|
||||
testURLCtorBase("http://Example.com/", "https://example.org/", "http://example.com/");
|
||||
testURLCtorBase("https://Example.com/", "https://example.org/", "https://example.com/");
|
||||
testURLCtorBase("foo://Example.com/", "https://example.org/", "foo://Example.com/");
|
||||
testURLCtorBase("foo:Example.com/", "https://example.org/", "foo:Example.com/");
|
||||
testURLCtorBase("#hash", "https://example.org/", "https://example.org/#hash");
|
||||
|
||||
testURLCtor("HTTP://test.com", "http://test.com/");
|
||||
testURLCtor("HTTPS://á.com", "https://xn--1ca.com/");
|
||||
testURLCtor("HTTPS://á.com:123", "https://xn--1ca.com:123/");
|
||||
testURLCtor("https://test.com#asdfá", "https://test.com/#asdf%C3%A1");
|
||||
testURLCtor("HTTPS://á.com:123/á", "https://xn--1ca.com:123/%C3%A1");
|
||||
testURLCtor("fish://á.com", "fish://%C3%A1.com");
|
||||
testURLCtor("https://test.com/?a=1 /2", "https://test.com/?a=1%20/2");
|
||||
testURLCtor("https://test.com/á=1?á=1&ü=2#é", "https://test.com/%C3%A1=1?%C3%A1=1&%C3%BC=2#%C3%A9");
|
||||
|
||||
assert.throws(() => new URL("test"), TypeError);
|
||||
assert.throws(() => new URL("ssh://EEE:ddd"), TypeError);
|
||||
|
||||
{
|
||||
let u = new URL("https://example.org/");
|
||||
assert.sameValue(u.__proto__.constructor, URL);
|
||||
assert.sameValue(u instanceof URL, true);
|
||||
}
|
||||
|
||||
{
|
||||
let u = new URL("https://example.org/");
|
||||
assert.sameValue(u.searchParams, u.searchParams);
|
||||
}
|
||||
|
||||
let myURL;
|
||||
|
||||
// Hash
|
||||
myURL = new URL("https://example.org/foo#bar");
|
||||
myURL.hash = "baz";
|
||||
assert.sameValue(myURL.href, "https://example.org/foo#baz");
|
||||
|
||||
myURL.hash = "#baz";
|
||||
assert.sameValue(myURL.href, "https://example.org/foo#baz");
|
||||
|
||||
myURL.hash = "#á=1 2";
|
||||
assert.sameValue(myURL.href, "https://example.org/foo#%C3%A1=1%202");
|
||||
|
||||
myURL.hash = "#a/#b";
|
||||
// FAILING: the second # gets escaped
|
||||
//assert.sameValue(myURL.href, "https://example.org/foo#a/#b");
|
||||
assert.sameValue(myURL.search, "");
|
||||
// FAILING: the second # gets escaped
|
||||
//assert.sameValue(myURL.hash, "#a/#b");
|
||||
|
||||
// Host
|
||||
myURL = new URL("https://example.org:81/foo");
|
||||
myURL.host = "example.com:82";
|
||||
assert.sameValue(myURL.href, "https://example.com:82/foo");
|
||||
|
||||
// Hostname
|
||||
myURL = new URL("https://example.org:81/foo");
|
||||
myURL.hostname = "example.com:82";
|
||||
assert.sameValue(myURL.href, "https://example.org:81/foo");
|
||||
|
||||
myURL.hostname = "á.com";
|
||||
assert.sameValue(myURL.href, "https://xn--1ca.com:81/foo");
|
||||
|
||||
// href
|
||||
myURL = new URL("https://example.org/foo");
|
||||
myURL.href = "https://example.com/bar";
|
||||
assert.sameValue(myURL.href, "https://example.com/bar");
|
||||
|
||||
// Password
|
||||
myURL = new URL("https://abc:xyz@example.com");
|
||||
myURL.password = "123";
|
||||
assert.sameValue(myURL.href, "https://abc:123@example.com/");
|
||||
|
||||
// pathname
|
||||
myURL = new URL("https://example.org/abc/xyz?123");
|
||||
myURL.pathname = "/abcdef";
|
||||
assert.sameValue(myURL.href, "https://example.org/abcdef?123");
|
||||
|
||||
myURL.pathname = "";
|
||||
assert.sameValue(myURL.href, "https://example.org/?123");
|
||||
|
||||
myURL.pathname = "á";
|
||||
assert.sameValue(myURL.pathname, "/%C3%A1");
|
||||
assert.sameValue(myURL.href, "https://example.org/%C3%A1?123");
|
||||
|
||||
// port
|
||||
|
||||
myURL = new URL("https://example.org:8888");
|
||||
assert.sameValue(myURL.port, "8888");
|
||||
|
||||
function testSetPort(port, expected) {
|
||||
const url = new URL("https://example.org:8888");
|
||||
url.port = port;
|
||||
assert.sameValue(url.port, expected);
|
||||
}
|
||||
|
||||
testSetPort(0, "0");
|
||||
testSetPort(-0, "0");
|
||||
|
||||
// Default ports are automatically transformed to the empty string
|
||||
// (HTTPS protocol's default port is 443)
|
||||
testSetPort("443", "");
|
||||
testSetPort(443, "");
|
||||
|
||||
// Empty string is the same as default port
|
||||
testSetPort("", "");
|
||||
|
||||
// Completely invalid port strings are ignored
|
||||
testSetPort("abcd", "8888");
|
||||
testSetPort("-123", "");
|
||||
testSetPort(-123, "");
|
||||
testSetPort(-123.45, "");
|
||||
testSetPort(undefined, "8888");
|
||||
testSetPort(null, "8888");
|
||||
testSetPort(+Infinity, "8888");
|
||||
testSetPort(-Infinity, "8888");
|
||||
testSetPort(NaN, "8888");
|
||||
|
||||
// Leading numbers are treated as a port number
|
||||
testSetPort("5678abcd", "5678");
|
||||
testSetPort("a5678abcd", "");
|
||||
|
||||
// Non-integers are truncated
|
||||
testSetPort(1234.5678, "1234");
|
||||
|
||||
// Out-of-range numbers which are not represented in scientific notation
|
||||
// will be ignored.
|
||||
testSetPort(1e10, "8888");
|
||||
testSetPort("123456", "8888");
|
||||
testSetPort(123456, "8888");
|
||||
testSetPort(4.567e21, "4");
|
||||
|
||||
// toString() takes precedence over valueOf(), even if it returns a valid integer
|
||||
testSetPort(
|
||||
{
|
||||
toString() {
|
||||
return "2";
|
||||
},
|
||||
valueOf() {
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
"2"
|
||||
);
|
||||
|
||||
// Protocol
|
||||
function testSetProtocol(url, protocol, expected) {
|
||||
url.protocol = protocol;
|
||||
assert.sameValue(url.protocol, expected);
|
||||
}
|
||||
testSetProtocol(new URL("https://example.org"), "ftp", "ftp:");
|
||||
testSetProtocol(new URL("https://example.org"), "ftp:", "ftp:");
|
||||
testSetProtocol(new URL("https://example.org"), "FTP:", "ftp:");
|
||||
testSetProtocol(new URL("https://example.org"), "ftp: blah", "ftp:");
|
||||
// special to non-special
|
||||
testSetProtocol(new URL("https://example.org"), "foo", "https:");
|
||||
// non-special to special
|
||||
testSetProtocol(new URL("fish://example.org"), "https", "fish:");
|
||||
|
||||
// Search
|
||||
myURL = new URL("https://example.org/abc?123");
|
||||
myURL.search = "abc=xyz";
|
||||
assert.sameValue(myURL.href, "https://example.org/abc?abc=xyz");
|
||||
|
||||
myURL.search = "a=1 2";
|
||||
assert.sameValue(myURL.href, "https://example.org/abc?a=1%202");
|
||||
|
||||
myURL.search = "á=ú";
|
||||
assert.sameValue(myURL.search, "?%C3%A1=%C3%BA");
|
||||
assert.sameValue(myURL.href, "https://example.org/abc?%C3%A1=%C3%BA");
|
||||
|
||||
myURL.hash = "hash";
|
||||
myURL.search = "a=#b";
|
||||
assert.sameValue(myURL.href, "https://example.org/abc?a=%23b#hash");
|
||||
assert.sameValue(myURL.search, "?a=%23b");
|
||||
assert.sameValue(myURL.hash, "#hash");
|
||||
|
||||
// Username
|
||||
myURL = new URL("https://abc:xyz@example.com/");
|
||||
myURL.username = "123";
|
||||
assert.sameValue(myURL.href, "https://123:xyz@example.com/");
|
||||
|
||||
// Origin, read-only
|
||||
assert.throws(() => {
|
||||
myURL.origin = "abc";
|
||||
}, TypeError);
|
||||
|
||||
// href
|
||||
myURL = new URL("https://example.org");
|
||||
myURL.href = "https://example.com";
|
||||
assert.sameValue(myURL.href, "https://example.com/");
|
||||
|
||||
assert.throws(() => {
|
||||
myURL.href = "test";
|
||||
}, TypeError);
|
||||
|
||||
// Search Params
|
||||
myURL = new URL("https://example.com/");
|
||||
myURL.searchParams.append("user", "abc");
|
||||
assert.sameValue(myURL.toString(), "https://example.com/?user=abc");
|
||||
myURL.searchParams.append("first", "one");
|
||||
assert.sameValue(myURL.toString(), "https://example.com/?user=abc&first=one");
|
||||
myURL.searchParams.delete("user");
|
||||
assert.sameValue(myURL.toString(), "https://example.com/?first=one");
|
||||
|
||||
{
|
||||
const url = require("url");
|
||||
|
||||
assert.sameValue(url.domainToASCII('español.com'), "xn--espaol-zwa.com");
|
||||
assert.sameValue(url.domainToASCII('中文.com'), "xn--fiq228c.com");
|
||||
assert.sameValue(url.domainToASCII('xn--iñvalid.com'), "");
|
||||
|
||||
assert.sameValue(url.domainToUnicode('xn--espaol-zwa.com'), "español.com");
|
||||
assert.sameValue(url.domainToUnicode('xn--fiq228c.com'), "中文.com");
|
||||
assert.sameValue(url.domainToUnicode('xn--iñvalid.com'), "");
|
||||
}
|
407
goja_nodejs/url/url.go
Normal file
407
goja_nodejs/url/url.go
Normal file
@ -0,0 +1,407 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/errors"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
URLNotAbsolute = "URL is not absolute"
|
||||
InvalidURL = "Invalid URL"
|
||||
InvalidBaseURL = "Invalid base URL"
|
||||
InvalidHostname = "Invalid hostname"
|
||||
)
|
||||
|
||||
var (
|
||||
reflectTypeURL = reflect.TypeOf((*nodeURL)(nil))
|
||||
reflectTypeInt = reflect.TypeOf(int64(0))
|
||||
)
|
||||
|
||||
func toURL(r *goja.Runtime, v goja.Value) *nodeURL {
|
||||
if v.ExportType() == reflectTypeURL {
|
||||
if u := v.Export().(*nodeURL); u != nil {
|
||||
return u
|
||||
}
|
||||
}
|
||||
|
||||
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URL`))
|
||||
}
|
||||
|
||||
func (m *urlModule) newInvalidURLError(msg, input string) *goja.Object {
|
||||
o := errors.NewTypeError(m.r, "ERR_INVALID_URL", msg)
|
||||
o.Set("input", m.r.ToValue(input))
|
||||
return o
|
||||
}
|
||||
|
||||
func (m *urlModule) defineURLAccessorProp(p *goja.Object, name string, getter func(*nodeURL) interface{}, setter func(*nodeURL, goja.Value)) {
|
||||
var getterVal, setterVal goja.Value
|
||||
if getter != nil {
|
||||
getterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
return m.r.ToValue(getter(toURL(m.r, call.This)))
|
||||
})
|
||||
}
|
||||
if setter != nil {
|
||||
setterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
setter(toURL(m.r, call.This), call.Argument(0))
|
||||
return goja.Undefined()
|
||||
})
|
||||
}
|
||||
p.DefineAccessorProperty(name, getterVal, setterVal, goja.FLAG_FALSE, goja.FLAG_TRUE)
|
||||
}
|
||||
|
||||
func valueToURLPort(v goja.Value) (portNum int, empty bool) {
|
||||
portNum = -1
|
||||
if et := v.ExportType(); et == reflectTypeInt {
|
||||
num := v.ToInteger()
|
||||
if num < 0 {
|
||||
empty = true
|
||||
} else if num <= math.MaxUint16 {
|
||||
portNum = int(num)
|
||||
}
|
||||
} else {
|
||||
s := v.String()
|
||||
if s == "" {
|
||||
return 0, true
|
||||
}
|
||||
firstDigitIdx := -1
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; c >= '0' && c <= '9' {
|
||||
firstDigitIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if firstDigitIdx == -1 {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
if firstDigitIdx > 0 {
|
||||
return 0, true
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; c >= '0' && c <= '9' {
|
||||
if portNum == -1 {
|
||||
portNum = 0
|
||||
}
|
||||
portNum = portNum*10 + int(c-'0')
|
||||
if portNum > math.MaxUint16 {
|
||||
portNum = -1
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isDefaultURLPort(protocol string, port int) bool {
|
||||
switch port {
|
||||
case 21:
|
||||
if protocol == "ftp" {
|
||||
return true
|
||||
}
|
||||
case 80:
|
||||
if protocol == "http" || protocol == "ws" {
|
||||
return true
|
||||
}
|
||||
case 443:
|
||||
if protocol == "https" || protocol == "wss" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSpecialProtocol(protocol string) bool {
|
||||
switch protocol {
|
||||
case "ftp", "file", "http", "https", "ws", "wss":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func clearURLPort(u *url.URL) {
|
||||
u.Host = u.Hostname()
|
||||
}
|
||||
|
||||
func setURLPort(nu *nodeURL, v goja.Value) {
|
||||
u := nu.url
|
||||
if u.Scheme == "file" {
|
||||
return
|
||||
}
|
||||
portNum, empty := valueToURLPort(v)
|
||||
if empty {
|
||||
clearURLPort(u)
|
||||
return
|
||||
}
|
||||
if portNum == -1 {
|
||||
return
|
||||
}
|
||||
if isDefaultURLPort(u.Scheme, portNum) {
|
||||
clearURLPort(u)
|
||||
} else {
|
||||
u.Host = u.Hostname() + ":" + strconv.Itoa(portNum)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *urlModule) parseURL(s string, isBase bool) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
if isBase {
|
||||
panic(m.newInvalidURLError(InvalidBaseURL, s))
|
||||
} else {
|
||||
panic(m.newInvalidURLError(InvalidURL, s))
|
||||
}
|
||||
}
|
||||
if isBase && !u.IsAbs() {
|
||||
panic(m.newInvalidURLError(URLNotAbsolute, s))
|
||||
}
|
||||
if portStr := u.Port(); portStr != "" {
|
||||
if port, err := strconv.Atoi(portStr); err != nil || isDefaultURLPort(u.Scheme, port) {
|
||||
u.Host = u.Hostname() // Clear port
|
||||
}
|
||||
}
|
||||
m.fixURL(u)
|
||||
return u
|
||||
}
|
||||
|
||||
func fixRawQuery(u *url.URL) {
|
||||
if u.RawQuery != "" {
|
||||
u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *urlModule) fixURL(u *url.URL) {
|
||||
switch u.Scheme {
|
||||
case "https", "http", "ftp", "wss", "ws":
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
hostname := u.Hostname()
|
||||
lh := strings.ToLower(hostname)
|
||||
ch, err := idna.Punycode.ToASCII(lh)
|
||||
if err != nil {
|
||||
panic(m.newInvalidURLError(InvalidHostname, lh))
|
||||
}
|
||||
if ch != hostname {
|
||||
if port := u.Port(); port != "" {
|
||||
u.Host = ch + ":" + port
|
||||
} else {
|
||||
u.Host = ch
|
||||
}
|
||||
}
|
||||
}
|
||||
fixRawQuery(u)
|
||||
}
|
||||
|
||||
func (m *urlModule) createURLPrototype() *goja.Object {
|
||||
p := m.r.NewObject()
|
||||
|
||||
// host
|
||||
m.defineURLAccessorProp(p, "host", func(u *nodeURL) interface{} {
|
||||
return u.url.Host
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
host := arg.String()
|
||||
if _, err := url.ParseRequestURI(u.url.Scheme + "://" + host); err == nil {
|
||||
u.url.Host = host
|
||||
m.fixURL(u.url)
|
||||
}
|
||||
})
|
||||
|
||||
// hash
|
||||
m.defineURLAccessorProp(p, "hash", func(u *nodeURL) interface{} {
|
||||
if u.url.Fragment != "" {
|
||||
return "#" + u.url.EscapedFragment()
|
||||
}
|
||||
return ""
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
h := arg.String()
|
||||
if len(h) > 0 && h[0] == '#' {
|
||||
h = h[1:]
|
||||
}
|
||||
u.url.Fragment = h
|
||||
})
|
||||
|
||||
// hostname
|
||||
m.defineURLAccessorProp(p, "hostname", func(u *nodeURL) interface{} {
|
||||
return strings.Split(u.url.Host, ":")[0]
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
h := arg.String()
|
||||
if strings.IndexByte(h, ':') >= 0 {
|
||||
return
|
||||
}
|
||||
if _, err := url.ParseRequestURI(u.url.Scheme + "://" + h); err == nil {
|
||||
if port := u.url.Port(); port != "" {
|
||||
u.url.Host = h + ":" + port
|
||||
} else {
|
||||
u.url.Host = h
|
||||
}
|
||||
m.fixURL(u.url)
|
||||
}
|
||||
})
|
||||
|
||||
// href
|
||||
m.defineURLAccessorProp(p, "href", func(u *nodeURL) interface{} {
|
||||
return u.String()
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
u.url = m.parseURL(arg.String(), true)
|
||||
})
|
||||
|
||||
// pathname
|
||||
m.defineURLAccessorProp(p, "pathname", func(u *nodeURL) interface{} {
|
||||
return u.url.EscapedPath()
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
p := arg.String()
|
||||
if _, err := url.Parse(p); err == nil {
|
||||
switch u.url.Scheme {
|
||||
case "https", "http", "ftp", "ws", "wss":
|
||||
if !strings.HasPrefix(p, "/") {
|
||||
p = "/" + p
|
||||
}
|
||||
}
|
||||
u.url.Path = p
|
||||
}
|
||||
})
|
||||
|
||||
// origin
|
||||
m.defineURLAccessorProp(p, "origin", func(u *nodeURL) interface{} {
|
||||
return u.url.Scheme + "://" + u.url.Hostname()
|
||||
}, nil)
|
||||
|
||||
// password
|
||||
m.defineURLAccessorProp(p, "password", func(u *nodeURL) interface{} {
|
||||
p, _ := u.url.User.Password()
|
||||
return p
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
user := u.url.User
|
||||
u.url.User = url.UserPassword(user.Username(), arg.String())
|
||||
})
|
||||
|
||||
// username
|
||||
m.defineURLAccessorProp(p, "username", func(u *nodeURL) interface{} {
|
||||
return u.url.User.Username()
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
p, has := u.url.User.Password()
|
||||
if !has {
|
||||
u.url.User = url.User(arg.String())
|
||||
} else {
|
||||
u.url.User = url.UserPassword(arg.String(), p)
|
||||
}
|
||||
})
|
||||
|
||||
// port
|
||||
m.defineURLAccessorProp(p, "port", func(u *nodeURL) interface{} {
|
||||
return u.url.Port()
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
setURLPort(u, arg)
|
||||
})
|
||||
|
||||
// protocol
|
||||
m.defineURLAccessorProp(p, "protocol", func(u *nodeURL) interface{} {
|
||||
return u.url.Scheme + ":"
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
s := arg.String()
|
||||
pos := strings.IndexByte(s, ':')
|
||||
if pos >= 0 {
|
||||
s = s[:pos]
|
||||
}
|
||||
s = strings.ToLower(s)
|
||||
if isSpecialProtocol(u.url.Scheme) == isSpecialProtocol(s) {
|
||||
if _, err := url.ParseRequestURI(s + "://" + u.url.Host); err == nil {
|
||||
u.url.Scheme = s
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Search
|
||||
m.defineURLAccessorProp(p, "search", func(u *nodeURL) interface{} {
|
||||
u.syncSearchParams()
|
||||
if u.url.RawQuery != "" {
|
||||
return "?" + u.url.RawQuery
|
||||
}
|
||||
return ""
|
||||
}, func(u *nodeURL, arg goja.Value) {
|
||||
u.url.RawQuery = arg.String()
|
||||
fixRawQuery(u.url)
|
||||
if u.searchParams != nil {
|
||||
u.searchParams = parseSearchQuery(u.url.RawQuery)
|
||||
if u.searchParams == nil {
|
||||
u.searchParams = make(searchParams, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// search Params
|
||||
m.defineURLAccessorProp(p, "searchParams", func(u *nodeURL) interface{} {
|
||||
if u.searchParams == nil {
|
||||
sp := parseSearchQuery(u.url.RawQuery)
|
||||
if sp == nil {
|
||||
sp = make(searchParams, 0)
|
||||
}
|
||||
u.searchParams = sp
|
||||
}
|
||||
return m.newURLSearchParams((*urlSearchParams)(u))
|
||||
}, nil)
|
||||
|
||||
p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toURL(m.r, call.This)
|
||||
u.syncSearchParams()
|
||||
return m.r.ToValue(u.url.String())
|
||||
}))
|
||||
|
||||
p.Set("toJSON", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toURL(m.r, call.This)
|
||||
u.syncSearchParams()
|
||||
return m.r.ToValue(u.url.String())
|
||||
}))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (m *urlModule) createURLConstructor() goja.Value {
|
||||
f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||
var u *url.URL
|
||||
if baseArg := call.Argument(1); !goja.IsUndefined(baseArg) {
|
||||
base := m.parseURL(baseArg.String(), true)
|
||||
ref := m.parseURL(call.Argument(0).String(), false)
|
||||
u = base.ResolveReference(ref)
|
||||
} else {
|
||||
u = m.parseURL(call.Argument(0).String(), true)
|
||||
}
|
||||
res := m.r.ToValue(&nodeURL{url: u}).(*goja.Object)
|
||||
res.SetPrototype(call.This.Prototype())
|
||||
return res
|
||||
}).(*goja.Object)
|
||||
|
||||
proto := m.createURLPrototype()
|
||||
f.Set("prototype", proto)
|
||||
proto.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
|
||||
return f
|
||||
}
|
||||
|
||||
func (m *urlModule) domainToASCII(domUnicode string) string {
|
||||
res, err := idna.ToASCII(domUnicode)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *urlModule) domainToUnicode(domASCII string) string {
|
||||
res, err := idna.ToUnicode(domASCII)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return res
|
||||
}
|
122
goja_nodejs/url/url_test.go
Normal file
122
goja_nodejs/url/url_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("URL"); c == nil {
|
||||
t.Fatal("URL not found")
|
||||
}
|
||||
|
||||
script := `const url = new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash");`
|
||||
|
||||
if _, err := vm.RunString(script); err != nil {
|
||||
t.Fatal("Failed to process url script.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetters(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("URL"); c == nil {
|
||||
t.Fatal("URL not found")
|
||||
}
|
||||
|
||||
script := `
|
||||
new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed");
|
||||
`
|
||||
|
||||
v, err := vm.RunString(script)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to process url script.", err)
|
||||
}
|
||||
|
||||
url := v.ToObject(vm)
|
||||
|
||||
tests := []struct {
|
||||
prop string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
prop: "hash",
|
||||
expected: "#hashed",
|
||||
},
|
||||
{
|
||||
prop: "host",
|
||||
expected: "sub.example.com:8080",
|
||||
},
|
||||
{
|
||||
prop: "hostname",
|
||||
expected: "sub.example.com",
|
||||
},
|
||||
{
|
||||
prop: "href",
|
||||
expected: "https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed",
|
||||
},
|
||||
{
|
||||
prop: "origin",
|
||||
expected: "https://sub.example.com",
|
||||
},
|
||||
{
|
||||
prop: "password",
|
||||
expected: "pass",
|
||||
},
|
||||
{
|
||||
prop: "username",
|
||||
expected: "user",
|
||||
},
|
||||
{
|
||||
prop: "port",
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
prop: "protocol",
|
||||
expected: "https:",
|
||||
},
|
||||
{
|
||||
prop: "search",
|
||||
expected: "?query=string",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
v := url.Get(test.prop).String()
|
||||
if v != test.expected {
|
||||
t.Fatal("failed to match " + test.prop + " property. got: " + v + ", expected: " + test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed testdata/url_test.js
|
||||
var urlTest string
|
||||
|
||||
func TestJs(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
Enable(vm)
|
||||
|
||||
if c := vm.Get("URL"); c == nil {
|
||||
t.Fatal("URL not found")
|
||||
}
|
||||
|
||||
// Script will throw an error on failed validation
|
||||
|
||||
_, err := vm.RunScript("testdata/url_test.js", urlTest)
|
||||
if err != nil {
|
||||
if ex, ok := err.(*goja.Exception); ok {
|
||||
t.Fatal(ex.String())
|
||||
}
|
||||
t.Fatal("Failed to process url script.", err)
|
||||
}
|
||||
}
|
390
goja_nodejs/url/urlsearchparams.go
Normal file
390
goja_nodejs/url/urlsearchparams.go
Normal file
@ -0,0 +1,390 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"apigo.cc/ai/ai/goja_nodejs/errors"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
)
|
||||
|
||||
var (
|
||||
reflectTypeURLSearchParams = reflect.TypeOf((*urlSearchParams)(nil))
|
||||
reflectTypeURLSearchParamsIterator = reflect.TypeOf((*urlSearchParamsIterator)(nil))
|
||||
)
|
||||
|
||||
func newInvalidTupleError(r *goja.Runtime) *goja.Object {
|
||||
return errors.NewTypeError(r, "ERR_INVALID_TUPLE", "Each query pair must be an iterable [name, value] tuple")
|
||||
}
|
||||
|
||||
func newMissingArgsError(r *goja.Runtime, msg string) *goja.Object {
|
||||
return errors.NewTypeError(r, errors.ErrCodeMissingArgs, msg)
|
||||
}
|
||||
|
||||
func newInvalidArgsError(r *goja.Runtime) *goja.Object {
|
||||
return errors.NewTypeError(r, "ERR_INVALID_ARG_TYPE", `The "callback" argument must be of type function.`)
|
||||
}
|
||||
|
||||
func toUrlSearchParams(r *goja.Runtime, v goja.Value) *urlSearchParams {
|
||||
if v.ExportType() == reflectTypeURLSearchParams {
|
||||
if u := v.Export().(*urlSearchParams); u != nil {
|
||||
return u
|
||||
}
|
||||
}
|
||||
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParams`))
|
||||
}
|
||||
|
||||
func (m *urlModule) newURLSearchParams(sp *urlSearchParams) *goja.Object {
|
||||
v := m.r.ToValue(sp).(*goja.Object)
|
||||
v.SetPrototype(m.URLSearchParamsPrototype)
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *urlModule) createURLSearchParamsConstructor() goja.Value {
|
||||
f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||
var sp searchParams
|
||||
v := call.Argument(0)
|
||||
if o, ok := v.(*goja.Object); ok {
|
||||
sp = m.buildParamsFromObject(o)
|
||||
} else if !goja.IsUndefined(v) {
|
||||
sp = parseSearchQuery(v.String())
|
||||
}
|
||||
|
||||
return m.newURLSearchParams(&urlSearchParams{searchParams: sp})
|
||||
}).(*goja.Object)
|
||||
|
||||
m.URLSearchParamsPrototype = m.createURLSearchParamsPrototype()
|
||||
f.Set("prototype", m.URLSearchParamsPrototype)
|
||||
m.URLSearchParamsPrototype.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (m *urlModule) buildParamsFromObject(o *goja.Object) searchParams {
|
||||
var query searchParams
|
||||
|
||||
if o.GetSymbol(goja.SymIterator) != nil {
|
||||
return m.buildParamsFromIterable(o)
|
||||
}
|
||||
|
||||
for _, k := range o.Keys() {
|
||||
val := o.Get(k).String()
|
||||
query = append(query, searchParam{name: k, value: val})
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (m *urlModule) buildParamsFromIterable(o *goja.Object) searchParams {
|
||||
var query searchParams
|
||||
|
||||
m.r.ForOf(o, func(val goja.Value) bool {
|
||||
obj := val.ToObject(m.r)
|
||||
var name, value string
|
||||
i := 0
|
||||
// Use ForOf to determine if the object is iterable
|
||||
m.r.ForOf(obj, func(val goja.Value) bool {
|
||||
if i == 0 {
|
||||
name = val.String()
|
||||
i++
|
||||
return true
|
||||
}
|
||||
if i == 1 {
|
||||
value = val.String()
|
||||
i++
|
||||
return true
|
||||
}
|
||||
// Array isn't a tuple
|
||||
panic(newInvalidTupleError(m.r))
|
||||
})
|
||||
|
||||
// Ensure we have two values
|
||||
if i <= 1 {
|
||||
panic(newInvalidTupleError(m.r))
|
||||
}
|
||||
|
||||
query = append(query, searchParam{
|
||||
name: name,
|
||||
value: value,
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (m *urlModule) createURLSearchParamsPrototype() *goja.Object {
|
||||
p := m.r.NewObject()
|
||||
|
||||
p.Set("append", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`))
|
||||
}
|
||||
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
u.searchParams = append(u.searchParams, searchParam{
|
||||
name: call.Argument(0).String(),
|
||||
value: call.Argument(1).String(),
|
||||
})
|
||||
u.markUpdated()
|
||||
|
||||
return goja.Undefined()
|
||||
}))
|
||||
|
||||
p.Set("delete", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) < 1 {
|
||||
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
|
||||
}
|
||||
|
||||
name := call.Argument(0).String()
|
||||
isValid := func(v searchParam) bool {
|
||||
if len(call.Arguments) == 1 {
|
||||
return v.name != name
|
||||
} else if v.name == name {
|
||||
arg := call.Argument(1)
|
||||
if !goja.IsUndefined(arg) && v.value == arg.String() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
j := 0
|
||||
for i, v := range u.searchParams {
|
||||
if isValid(v) {
|
||||
if i != j {
|
||||
u.searchParams[j] = v
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
u.searchParams = u.searchParams[:j]
|
||||
u.markUpdated()
|
||||
|
||||
return goja.Undefined()
|
||||
}))
|
||||
|
||||
entries := m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorEntries)
|
||||
})
|
||||
p.Set("entries", entries)
|
||||
p.DefineDataPropertySymbol(goja.SymIterator, entries, goja.FLAG_TRUE, goja.FLAG_FALSE, goja.FLAG_TRUE)
|
||||
|
||||
p.Set("forEach", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) != 1 {
|
||||
panic(newInvalidArgsError(m.r))
|
||||
}
|
||||
|
||||
if fn, ok := goja.AssertFunction(call.Argument(0)); ok {
|
||||
for _, pair := range u.searchParams {
|
||||
// name, value, searchParams
|
||||
_, err := fn(
|
||||
nil,
|
||||
m.r.ToValue(pair.name),
|
||||
m.r.ToValue(pair.value),
|
||||
call.This,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic(newInvalidArgsError(m.r))
|
||||
}
|
||||
|
||||
return goja.Undefined()
|
||||
}))
|
||||
|
||||
p.Set("get", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) == 0 {
|
||||
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
|
||||
}
|
||||
|
||||
if val, exists := u.getFirstValue(call.Argument(0).String()); exists {
|
||||
return m.r.ToValue(val)
|
||||
}
|
||||
|
||||
return goja.Null()
|
||||
}))
|
||||
|
||||
p.Set("getAll", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) == 0 {
|
||||
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
|
||||
}
|
||||
|
||||
vals := u.getValues(call.Argument(0).String())
|
||||
return m.r.ToValue(vals)
|
||||
}))
|
||||
|
||||
p.Set("has", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) == 0 {
|
||||
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
|
||||
}
|
||||
|
||||
name := call.Argument(0).String()
|
||||
value := call.Argument(1)
|
||||
var res bool
|
||||
if goja.IsUndefined(value) {
|
||||
res = u.hasName(name)
|
||||
} else {
|
||||
res = u.hasValue(name, value.String())
|
||||
}
|
||||
return m.r.ToValue(res)
|
||||
}))
|
||||
|
||||
p.Set("keys", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorKeys)
|
||||
}))
|
||||
|
||||
p.Set("set", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`))
|
||||
}
|
||||
|
||||
name := call.Argument(0).String()
|
||||
found := false
|
||||
j := 0
|
||||
for i, sp := range u.searchParams {
|
||||
if sp.name == name {
|
||||
if found {
|
||||
continue // Remove all values
|
||||
}
|
||||
|
||||
u.searchParams[i].value = call.Argument(1).String()
|
||||
found = true
|
||||
}
|
||||
if i != j {
|
||||
u.searchParams[j] = sp
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
if !found {
|
||||
u.searchParams = append(u.searchParams, searchParam{
|
||||
name: name,
|
||||
value: call.Argument(1).String(),
|
||||
})
|
||||
} else {
|
||||
u.searchParams = u.searchParams[:j]
|
||||
}
|
||||
|
||||
u.markUpdated()
|
||||
|
||||
return goja.Undefined()
|
||||
}))
|
||||
|
||||
p.Set("sort", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
sort.Stable(u.searchParams)
|
||||
u.markUpdated()
|
||||
return goja.Undefined()
|
||||
}))
|
||||
|
||||
p.DefineAccessorProperty("size", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
return m.r.ToValue(len(u.searchParams))
|
||||
}), nil, goja.FLAG_FALSE, goja.FLAG_TRUE)
|
||||
|
||||
p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
u := toUrlSearchParams(m.r, call.This)
|
||||
str := u.searchParams.Encode()
|
||||
return m.r.ToValue(str)
|
||||
}))
|
||||
|
||||
p.Set("values", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorValues)
|
||||
}))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (sp *urlSearchParams) markUpdated() {
|
||||
if sp.url != nil && sp.url.RawQuery != "" {
|
||||
sp.url.RawQuery = ""
|
||||
}
|
||||
}
|
||||
|
||||
type urlSearchParamsIteratorType int
|
||||
|
||||
const (
|
||||
urlSearchParamsIteratorKeys urlSearchParamsIteratorType = iota
|
||||
urlSearchParamsIteratorValues
|
||||
urlSearchParamsIteratorEntries
|
||||
)
|
||||
|
||||
type urlSearchParamsIterator struct {
|
||||
typ urlSearchParamsIteratorType
|
||||
sp *urlSearchParams
|
||||
idx int
|
||||
}
|
||||
|
||||
func toURLSearchParamsIterator(r *goja.Runtime, v goja.Value) *urlSearchParamsIterator {
|
||||
if v.ExportType() == reflectTypeURLSearchParamsIterator {
|
||||
if u := v.Export().(*urlSearchParamsIterator); u != nil {
|
||||
return u
|
||||
}
|
||||
}
|
||||
|
||||
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParamIterator`))
|
||||
}
|
||||
|
||||
func (m *urlModule) getURLSearchParamsIteratorPrototype() *goja.Object {
|
||||
if m.URLSearchParamsIteratorPrototype != nil {
|
||||
return m.URLSearchParamsIteratorPrototype
|
||||
}
|
||||
|
||||
p := m.r.NewObject()
|
||||
|
||||
p.Set("next", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
|
||||
it := toURLSearchParamsIterator(m.r, call.This)
|
||||
res := m.r.NewObject()
|
||||
if it.idx < len(it.sp.searchParams) {
|
||||
param := it.sp.searchParams[it.idx]
|
||||
switch it.typ {
|
||||
case urlSearchParamsIteratorKeys:
|
||||
res.Set("value", param.name)
|
||||
case urlSearchParamsIteratorValues:
|
||||
res.Set("value", param.value)
|
||||
default:
|
||||
res.Set("value", m.r.NewArray(param.name, param.value))
|
||||
}
|
||||
res.Set("done", false)
|
||||
it.idx++
|
||||
} else {
|
||||
res.Set("value", goja.Undefined())
|
||||
res.Set("done", true)
|
||||
}
|
||||
return res
|
||||
}))
|
||||
|
||||
p.DefineDataPropertySymbol(goja.SymToStringTag, m.r.ToValue("URLSearchParams Iterator"), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE)
|
||||
|
||||
m.URLSearchParamsIteratorPrototype = p
|
||||
return p
|
||||
}
|
||||
|
||||
func (m *urlModule) newURLSearchParamsIterator(sp *urlSearchParams, typ urlSearchParamsIteratorType) goja.Value {
|
||||
it := m.r.ToValue(&urlSearchParamsIterator{
|
||||
typ: typ,
|
||||
sp: sp,
|
||||
}).(*goja.Object)
|
||||
|
||||
it.SetPrototype(m.getURLSearchParamsIteratorPrototype())
|
||||
|
||||
return it
|
||||
}
|
53
goja_nodejs/url/urlsearchparams_test.go
Normal file
53
goja_nodejs/url/urlsearchparams_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/console"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func createVM() *goja.Runtime {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
console.Enable(vm)
|
||||
Enable(vm)
|
||||
return vm
|
||||
}
|
||||
|
||||
func TestURLSearchParams(t *testing.T) {
|
||||
vm := createVM()
|
||||
|
||||
if c := vm.Get("URLSearchParams"); c == nil {
|
||||
t.Fatal("URLSearchParams not found")
|
||||
}
|
||||
|
||||
script := `const params = new URLSearchParams();`
|
||||
|
||||
if _, err := vm.RunString(script); err != nil {
|
||||
t.Fatal("Failed to process url script.", err)
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed testdata/url_search_params.js
|
||||
var url_search_params string
|
||||
|
||||
func TestURLSearchParameters(t *testing.T) {
|
||||
vm := createVM()
|
||||
|
||||
if c := vm.Get("URLSearchParams"); c == nil {
|
||||
t.Fatal("URLSearchParams not found")
|
||||
}
|
||||
|
||||
// Script will throw an error on failed validation
|
||||
|
||||
_, err := vm.RunScript("testdata/url_search_params.js", url_search_params)
|
||||
if err != nil {
|
||||
if ex, ok := err.(*goja.Exception); ok {
|
||||
t.Fatal(ex.String())
|
||||
}
|
||||
t.Fatal("Failed to process url script.", err)
|
||||
}
|
||||
}
|
104
goja_nodejs/util/module.go
Normal file
104
goja_nodejs/util/module.go
Normal file
@ -0,0 +1,104 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const ModuleName = "util"
|
||||
|
||||
type Util struct {
|
||||
runtime *goja.Runtime
|
||||
}
|
||||
|
||||
func (u *Util) format(f rune, val goja.Value, w *bytes.Buffer) bool {
|
||||
switch f {
|
||||
case 's':
|
||||
w.WriteString(val.String())
|
||||
case 'd':
|
||||
w.WriteString(val.ToNumber().String())
|
||||
case 'j':
|
||||
if json, ok := u.runtime.Get("JSON").(*goja.Object); ok {
|
||||
if stringify, ok := goja.AssertFunction(json.Get("stringify")); ok {
|
||||
res, err := stringify(json, val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.WriteString(res.String())
|
||||
}
|
||||
}
|
||||
case '%':
|
||||
w.WriteByte('%')
|
||||
return false
|
||||
default:
|
||||
w.WriteByte('%')
|
||||
w.WriteRune(f)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *Util) Format(b *bytes.Buffer, f string, args ...goja.Value) {
|
||||
pct := false
|
||||
argNum := 0
|
||||
for _, chr := range f {
|
||||
if pct {
|
||||
if argNum < len(args) {
|
||||
if u.format(chr, args[argNum], b) {
|
||||
argNum++
|
||||
}
|
||||
} else {
|
||||
b.WriteByte('%')
|
||||
b.WriteRune(chr)
|
||||
}
|
||||
pct = false
|
||||
} else {
|
||||
if chr == '%' {
|
||||
pct = true
|
||||
} else {
|
||||
b.WriteRune(chr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range args[argNum:] {
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(arg.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Util) js_format(call goja.FunctionCall) goja.Value {
|
||||
var b bytes.Buffer
|
||||
var fmt string
|
||||
|
||||
if arg := call.Argument(0); !goja.IsUndefined(arg) {
|
||||
fmt = arg.String()
|
||||
}
|
||||
|
||||
var args []goja.Value
|
||||
if len(call.Arguments) > 0 {
|
||||
args = call.Arguments[1:]
|
||||
}
|
||||
u.Format(&b, fmt, args...)
|
||||
|
||||
return u.runtime.ToValue(b.String())
|
||||
}
|
||||
|
||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||
u := &Util{
|
||||
runtime: runtime,
|
||||
}
|
||||
obj := module.Get("exports").(*goja.Object)
|
||||
obj.Set("format", u.js_format)
|
||||
}
|
||||
|
||||
func New(runtime *goja.Runtime) *Util {
|
||||
return &Util{
|
||||
runtime: runtime,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
require.RegisterCoreModule(ModuleName, Require)
|
||||
}
|
74
goja_nodejs/util/module_test.go
Normal file
74
goja_nodejs/util/module_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func TestUtil_Format(t *testing.T) {
|
||||
vm := goja.New()
|
||||
util := New(vm)
|
||||
|
||||
var b bytes.Buffer
|
||||
util.Format(&b, "Test: %% %д %s %d, %j", vm.ToValue("string"), vm.ToValue(42), vm.NewObject())
|
||||
|
||||
if res := b.String(); res != "Test: % %д string 42, {}" {
|
||||
t.Fatalf("Unexpected result: '%s'", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtil_Format_NoArgs(t *testing.T) {
|
||||
vm := goja.New()
|
||||
util := New(vm)
|
||||
|
||||
var b bytes.Buffer
|
||||
util.Format(&b, "Test: %s %d, %j")
|
||||
|
||||
if res := b.String(); res != "Test: %s %d, %j" {
|
||||
t.Fatalf("Unexpected result: '%s'", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtil_Format_LessArgs(t *testing.T) {
|
||||
vm := goja.New()
|
||||
util := New(vm)
|
||||
|
||||
var b bytes.Buffer
|
||||
util.Format(&b, "Test: %s %d, %j", vm.ToValue("string"), vm.ToValue(42))
|
||||
|
||||
if res := b.String(); res != "Test: string 42, %j" {
|
||||
t.Fatalf("Unexpected result: '%s'", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtil_Format_MoreArgs(t *testing.T) {
|
||||
vm := goja.New()
|
||||
util := New(vm)
|
||||
|
||||
var b bytes.Buffer
|
||||
util.Format(&b, "Test: %s %d, %j", vm.ToValue("string"), vm.ToValue(42), vm.NewObject(), vm.ToValue(42.42))
|
||||
|
||||
if res := b.String(); res != "Test: string 42, {} 42.42" {
|
||||
t.Fatalf("Unexpected result: '%s'", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSNoArgs(t *testing.T) {
|
||||
vm := goja.New()
|
||||
new(require.Registry).Enable(vm)
|
||||
|
||||
if util, ok := require.Require(vm, ModuleName).(*goja.Object); ok {
|
||||
if format, ok := goja.AssertFunction(util.Get("format")); ok {
|
||||
res, err := format(util)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := res.Export(); v != "" {
|
||||
t.Fatalf("Unexpected result: %v", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
js.go
4
js.go
@ -1,6 +1,8 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/goja_nodejs/require"
|
||||
"apigo.cc/ai/ai/js"
|
||||
"apigo.cc/ai/ai/llm"
|
||||
"bytes"
|
||||
@ -8,8 +10,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/ssgo/u"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
47
js/ai.go
47
js/ai.go
@ -1,8 +1,8 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"apigo.cc/ai/ai/llm"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/ssgo/u"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -24,81 +24,96 @@ type AIGCResult struct {
|
||||
|
||||
func RequireAI(lm llm.LLM) map[string]any {
|
||||
return map[string]any{
|
||||
"ask": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"ask": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
conf, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.Ask(makeChatMessages(args.Arguments), conf, cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"fastAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"fastAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.FastAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"longAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"longAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.LongAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"batterAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"batterAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.BatterAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"bestAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"bestAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.BestAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
|
||||
"multiAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"multiAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.MultiAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"bestMultiAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"bestMultiAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.BestMultiAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
|
||||
"codeInterpreterAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"codeInterpreterAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.CodeInterpreterAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
"webSearchAsk": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"webSearchAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
_, cb := getAskArgs(args.This, vm, args.Arguments)
|
||||
result, usage, err := lm.WebSearchAsk(makeChatMessages(args.Arguments), cb)
|
||||
return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)})
|
||||
},
|
||||
|
||||
"makeImage": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"makeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, err := lm.MakeImage(prompt, conf)
|
||||
return makeAIGCResult(vm, results, nil, err)
|
||||
},
|
||||
"fastMakeImage": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"fastMakeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, err := lm.FastMakeImage(prompt, conf)
|
||||
return makeAIGCResult(vm, results, nil, err)
|
||||
},
|
||||
"bestMakeImage": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"bestMakeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, err := lm.BestMakeImage(prompt, conf)
|
||||
return makeAIGCResult(vm, results, nil, err)
|
||||
},
|
||||
|
||||
"makeVideo": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"makeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, previews, err := lm.MakeVideo(prompt, conf)
|
||||
return makeAIGCResult(vm, results, previews, err)
|
||||
},
|
||||
"fastMakeVideo": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"fastMakeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, previews, err := lm.FastMakeVideo(prompt, conf)
|
||||
return makeAIGCResult(vm, results, previews, err)
|
||||
},
|
||||
"bestMakeVideo": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
"bestMakeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
prompt, conf := getAIGCArgs(args.Arguments)
|
||||
results, previews, err := lm.BestMakeVideo(prompt, conf)
|
||||
return makeAIGCResult(vm, results, previews, err)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"errors"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/ssgo/u"
|
||||
"path/filepath"
|
||||
)
|
||||
@ -25,11 +25,11 @@ func MakeArgs(args *goja.FunctionCall, vm *goja.Runtime) *Args {
|
||||
}
|
||||
}
|
||||
|
||||
func (args *Args) Check(num int) goja.Value {
|
||||
func (args *Args) Check(num int) *Args {
|
||||
if len(args.Arguments) < num {
|
||||
return args.VM.NewGoError(errors.New("arguments need " + u.String(num) + ", but given " + u.String(len(args.Arguments))))
|
||||
panic(args.VM.NewGoError(errors.New("arguments need " + u.String(num) + ", but given " + u.String(len(args.Arguments)))))
|
||||
}
|
||||
return nil
|
||||
return args
|
||||
}
|
||||
|
||||
func (args *Args) Int(index int) int {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"fmt"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/ssgo/u"
|
||||
"strings"
|
||||
)
|
||||
|
47
js/file.go
47
js/file.go
@ -1,54 +1,43 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dop251/goja"
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"github.com/ssgo/u"
|
||||
)
|
||||
|
||||
func RequireFile() map[string]any {
|
||||
return map[string]any{
|
||||
"read": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
if len(args.Arguments) < 1 {
|
||||
return vm.NewGoError(errors.New("arguments need 1, but given " + u.String(len(args.Arguments))))
|
||||
}
|
||||
if r, err := u.ReadFile(findPath(vm, u.String(args.Arguments[0].Export()))); err == nil {
|
||||
"read": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := u.ReadFile(findPath(vm, args.Str(0))); err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"write": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
if len(args.Arguments) < 2 {
|
||||
return vm.NewGoError(errors.New("arguments need 2, but given " + u.String(len(args.Arguments))))
|
||||
}
|
||||
if err := u.WriteFileBytes(findPath(vm, u.String(args.Arguments[0].Export())), u.Bytes(args.Arguments[0].Export())); err == nil {
|
||||
"write": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
if err := u.WriteFileBytes(findPath(vm, args.Str(0)), u.Bytes(args.Any(0))); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"dir": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
if len(args.Arguments) < 1 {
|
||||
return vm.NewGoError(errors.New("arguments need 1, but given " + u.String(len(args.Arguments))))
|
||||
}
|
||||
if r, err := u.ReadDir(findPath(vm, u.String(args.Arguments[0].Export()))); err == nil {
|
||||
"dir": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := u.ReadDir(findPath(vm, args.Str(0))); err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"stat": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
if len(args.Arguments) < 1 {
|
||||
return vm.NewGoError(errors.New("arguments need 1, but given " + u.String(len(args.Arguments))))
|
||||
}
|
||||
return vm.ToValue(u.GetFileInfo(findPath(vm, u.String(args.Arguments[0].Export()))))
|
||||
"stat": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.GetFileInfo(findPath(vm, args.Str(0))))
|
||||
},
|
||||
"find": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
if len(args.Arguments) < 1 {
|
||||
return vm.NewGoError(errors.New("arguments need 1, but given " + u.String(len(args.Arguments))))
|
||||
}
|
||||
return vm.ToValue(findPath(vm, u.String(args.Arguments[0].Export())))
|
||||
"find": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(findPath(vm, args.Str(0)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
44
js/http.go
44
js/http.go
@ -1,7 +1,7 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja"
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"github.com/ssgo/httpclient"
|
||||
"github.com/ssgo/u"
|
||||
)
|
||||
@ -41,7 +41,7 @@ func RequireHTTP() map[string]any {
|
||||
|
||||
func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value {
|
||||
if r.Error != nil {
|
||||
return vm.NewGoError(r.Error)
|
||||
panic(vm.NewGoError(r.Error))
|
||||
}
|
||||
headers := map[string]string{}
|
||||
for k, v := range r.Response.Header {
|
||||
@ -57,50 +57,32 @@ func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value {
|
||||
}
|
||||
|
||||
func (hc *Http) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return makeResult(hc.client.Get(args.Str(0), args.Map2StrArr(1)...), vm)
|
||||
}
|
||||
|
||||
func (hc *Http) Head(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return makeResult(hc.client.Head(args.Str(0), args.Map2StrArr(1)...), vm)
|
||||
}
|
||||
|
||||
func (hc *Http) Post(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
return makeResult(hc.client.Post(args.Str(0), args.Any(1), args.Map2StrArr(2)...), vm)
|
||||
}
|
||||
|
||||
func (hc *Http) Put(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
return makeResult(hc.client.Put(args.Str(0), args.Any(1), args.Map2StrArr(2)...), vm)
|
||||
}
|
||||
|
||||
func (hc *Http) Delete(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
return makeResult(hc.client.Delete(args.Str(0), args.Any(1), args.Map2StrArr(2)...), vm)
|
||||
}
|
||||
|
||||
func (hc *Http) Do(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(3); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(3)
|
||||
if len(argsIn.Arguments) == 3 {
|
||||
argsIn.Arguments = append(argsIn.Arguments, vm.ToValue(nil))
|
||||
}
|
||||
@ -122,10 +104,7 @@ func (hc *Http) Do(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
}
|
||||
|
||||
func (hc *Http) Upload(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
postData := map[string]string{}
|
||||
postFiles := map[string]any{}
|
||||
u.Convert(args.Any(1), &postData)
|
||||
@ -137,10 +116,7 @@ func (hc *Http) Upload(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
}
|
||||
|
||||
func (hc *Http) Download(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
var r *httpclient.Result
|
||||
var callback goja.Callable
|
||||
if len(argsIn.Arguments) > 2 {
|
||||
|
191
js/util.go
191
js/util.go
@ -1,35 +1,28 @@
|
||||
package js
|
||||
|
||||
import (
|
||||
"apigo.cc/ai/ai/goja"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/ssgo/u"
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func RequireUtil() map[string]any {
|
||||
return map[string]any{
|
||||
"json": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := json.Marshal(args.Arguments[0].Export()); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"jsonP": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := json.Marshal(args.Arguments[0].Export()); err == nil {
|
||||
r1 := bytes.Buffer{}
|
||||
if err2 := json.Indent(&r1, r, "", " "); err2 == nil {
|
||||
@ -38,145 +31,131 @@ func RequireUtil() map[string]any {
|
||||
return vm.ToValue(string(r))
|
||||
}
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"unJson": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
var r any
|
||||
if err := json.Unmarshal(u.Bytes(args.Arguments[0].Export()), &r); err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"yaml": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := yaml.Marshal(args.Arguments[0].Export()); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"unYaml": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
var r any
|
||||
if err := yaml.Unmarshal(u.Bytes(args.Arguments[0].Export()), &r); err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"save": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
filename := args.Str(0)
|
||||
var data []byte
|
||||
var err error
|
||||
if strings.HasSuffix(filename, ".yml") || strings.HasSuffix(filename, ".yaml") {
|
||||
data, err = yaml.Marshal(args.Any(1))
|
||||
} else {
|
||||
data, err = json.Marshal(args.Any(1))
|
||||
}
|
||||
if err == nil {
|
||||
if err = u.WriteFileBytes(findPath(vm, filename), data); err != nil {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"load": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
filename := args.Str(0)
|
||||
if data, err := u.ReadFileBytes(findPath(vm, filename)); err == nil {
|
||||
var r any
|
||||
if strings.HasSuffix(filename, ".yml") || strings.HasSuffix(filename, ".yaml") {
|
||||
err = yaml.Unmarshal(data, &r)
|
||||
} else {
|
||||
err = json.Unmarshal(data, &r)
|
||||
}
|
||||
if err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
} else {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"base64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.Base64(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"unBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.UnBase64String(args.Str(0)))
|
||||
},
|
||||
"urlBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.UrlBase64String(args.Str(0)))
|
||||
},
|
||||
"unUrlBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.UnUrlBase64String(args.Str(0)))
|
||||
},
|
||||
"hex": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(hex.EncodeToString(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"unHex": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := hex.DecodeString(args.Str(0)); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"aes": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(3)
|
||||
if r, err := u.EncryptAesBytes(u.Bytes(args.Arguments[0].Export()), u.Bytes(args.Arguments[1].Export()), u.Bytes(args.Arguments[2].Export())); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"unAes": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(3)
|
||||
if r, err := u.DecryptAesBytes(u.Bytes(args.Arguments[0].Export()), u.Bytes(args.Arguments[1].Export()), u.Bytes(args.Arguments[2].Export())); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"gzip": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := u.Gzip(u.Bytes(args.Arguments[0].Export())); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"gunzip": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
if r, err := u.Gunzip(u.Bytes(args.Arguments[0].Export())); err == nil {
|
||||
return vm.ToValue(string(r))
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
"id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
@ -193,43 +172,23 @@ func RequireUtil() map[string]any {
|
||||
return vm.ToValue(u.MakeToken(size))
|
||||
},
|
||||
"md5": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.MD5(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"sha1": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.Sha1(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"sha256": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.Sha256(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"sha512": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
return vm.ToValue(u.Sha512(u.Bytes(args.Arguments[0].Export())))
|
||||
},
|
||||
"tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(2)
|
||||
var functions = map[string]any{}
|
||||
if len(argsIn.Arguments) > 2 {
|
||||
obj := argsIn.Arguments[2].ToObject(vm)
|
||||
@ -256,16 +215,12 @@ func RequireUtil() map[string]any {
|
||||
err = tpl.Execute(buf, args.Arguments[1].Export())
|
||||
}
|
||||
if err != nil {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
return vm.ToValue(buf.String())
|
||||
},
|
||||
"shell": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := MakeArgs(&argsIn, vm)
|
||||
if err := args.Check(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := MakeArgs(&argsIn, vm).Check(1)
|
||||
a := make([]string, len(args.Arguments)-1)
|
||||
for i := 1; i < len(args.Arguments); i++ {
|
||||
a[i-1] = args.Str(i)
|
||||
@ -274,7 +229,7 @@ func RequireUtil() map[string]any {
|
||||
if r, err := u.RunCommand(args.Str(0), a...); err == nil {
|
||||
return vm.ToValue(r)
|
||||
} else {
|
||||
return vm.NewGoError(err)
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user