├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependency-review.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_zh-cn.md ├── go.mod ├── go.sum ├── inject.go ├── pkg ├── base64 │ ├── base64.go │ ├── base64_test.go │ └── inject.go ├── console │ ├── console.go │ ├── console_test.go │ └── inject.go ├── fetch │ ├── fetch.go │ ├── fetch_test.go │ └── inject.go └── window │ ├── inject.go │ ├── js │ └── window.js │ ├── window.go │ └── window_test.go └── polyfill_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "gomod" # See documentation for possible values 14 | directory: "/" # Location of package manifests 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '21 3 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go', 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v4 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | 11 | build: 12 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | go: ['1.20.x', '1.21.x'] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ matrix.go }} 26 | 27 | - name: Build 28 | run: go build -v ./... 29 | 30 | - name: Test 31 | run: go test -v -coverprofile c.out ./... 32 | 33 | - name: Codecov 34 | uses: codecov/codecov-action@v5.3.1 35 | env: 36 | OS: ${{ matrix.os }} 37 | GO: ${{ matrix.go }} 38 | with: 39 | files: ./c.out 40 | env_vars: OS,GO 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .vscode/ 18 | 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Brian Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polyfill for [quickjs-go](https://github.com/buke/quickjs-go) 2 | English | [简体中文](README_zh-cn.md) 3 | 4 | [![Test](https://github.com/buke/quickjs-go-polyfill/workflows/Test/badge.svg)](https://github.com/buke/quickjs-go-polyfill/actions?query=workflow%3ATest) 5 | [![codecov](https://codecov.io/gh/buke/quickjs-go-polyfill/branch/main/graph/badge.svg?token=4r8TboEuuJ)](https://codecov.io/gh/buke/quickjs-go-polyfill) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/buke/quickjs-go-polyfill)](https://goreportcard.com/report/github.com/buke/quickjs-go-polyfill) 7 | [![GoDoc](https://pkg.go.dev/badge/github.com/buke/quickjs-go-polyfill?status.svg)](https://pkg.go.dev/github.com/buke/quickjs-go-polyfill?tab=doc) 8 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill?ref=badge_shield) 9 | 10 | ## Features 11 | * fetch: `fetch` 12 | * base64: `atob` and `btoa` 13 | * window: `window` 14 | * console: `console.log` and `console.error` and `console.warn` and `console.info` and `console.debug` and `console.trace` 15 | * timers: `setTimeout` and `clearTimeout` alreay supported by quickjs-go 16 | 17 | ### Usage 18 | ```go 19 | package main 20 | import ( 21 | "time" 22 | 23 | "github.com/buke/quickjs-go" 24 | polyfill "github.com/buke/quickjs-go-polyfill" 25 | ) 26 | 27 | func main() { 28 | // Create a new runtime 29 | rt := quickjs.NewRuntime() 30 | defer rt.Close() 31 | 32 | // Create a new context 33 | ctx := rt.NewContext() 34 | defer ctx.Close() 35 | 36 | // Inject polyfills to the context 37 | polyfill.InjectAll(ctx) 38 | 39 | ret, _ := ctx.Eval(` 40 | setTimeout(() => { 41 | fetch('https://api.github.com/users/buke', {Method: 'GET'}).then(response => response.json()).then(data => { 42 | console.log(data.login); 43 | }); 44 | }, 50); 45 | `) 46 | defer ret.Free() 47 | 48 | // Wait for the timeout to finish 49 | time.Sleep(time.Millisecond * 100) 50 | 51 | rt.ExecuteAllPendingJobs() 52 | 53 | // Output: 54 | // buke 55 | } 56 | ``` 57 | 58 | ## Documentation 59 | Go Reference & more examples: https://pkg.go.dev/github.com/buke/quickjs-go-polyfill 60 | 61 | ## License 62 | [MIT](./LICENSE) 63 | 64 | 65 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill?ref=badge_large) -------------------------------------------------------------------------------- /README_zh-cn.md: -------------------------------------------------------------------------------- 1 | # Polyfill for [quickjs-go](https://github.com/buke/quickjs-go) 2 | [English](README.md) | 简体中文 3 | 4 | [![Test](https://github.com/buke/quickjs-go-polyfill/workflows/Test/badge.svg)](https://github.com/buke/quickjs-go-polyfill/actions?query=workflow%3ATest) 5 | [![codecov](https://codecov.io/gh/buke/quickjs-go-polyfill/branch/main/graph/badge.svg?token=4r8TboEuuJ)](https://codecov.io/gh/buke/quickjs-go-polyfill) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/buke/quickjs-go-polyfill)](https://goreportcard.com/report/github.com/buke/quickjs-go-polyfill) 7 | [![GoDoc](https://pkg.go.dev/badge/github.com/buke/quickjs-go-polyfill?status.svg)](https://pkg.go.dev/github.com/buke/quickjs-go-polyfill?tab=doc) 8 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill?ref=badge_shield) 9 | 10 | ## 功能 11 | * fetch: `fetch` 12 | * base64: `atob` and `btoa` 13 | * window: `window` 14 | * console: `console.log` and `console.error` and `console.warn` and `console.info` and `console.debug` and `console.trace` 15 | * timers: `setTimeout` and `clearTimeout` alreay supported by quickjs-go 16 | 17 | ### 用法 18 | ```go 19 | package main 20 | import ( 21 | "time" 22 | 23 | "github.com/buke/quickjs-go" 24 | polyfill "github.com/buke/quickjs-go-polyfill" 25 | ) 26 | 27 | func main() { 28 | // Create a new runtime 29 | rt := quickjs.NewRuntime() 30 | defer rt.Close() 31 | 32 | // Create a new context 33 | ctx := rt.NewContext() 34 | defer ctx.Close() 35 | 36 | // Inject polyfills to the context 37 | polyfill.InjectAll(ctx) 38 | 39 | ret, _ := ctx.Eval(` 40 | setTimeout(() => { 41 | fetch('https://api.github.com/users/buke', {Method: 'GET'}).then(response => response.json()).then(data => { 42 | console.log(data.login); 43 | }); 44 | }, 50); 45 | `) 46 | defer ret.Free() 47 | 48 | // Wait for the timeout to finish 49 | time.Sleep(time.Millisecond * 100) 50 | 51 | rt.ExecuteAllPendingJobs() 52 | 53 | // Output: 54 | // buke 55 | } 56 | ``` 57 | 58 | ## 文档 59 | Go 语言文档和示例: https://pkg.go.dev/github.com/buke/quickjs-go-polyfill 60 | 61 | ## 版权协议 62 | [MIT](./LICENSE) 63 | 64 | 65 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbuke%2Fquickjs-go-polyfill?ref=badge_large) -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/buke/quickjs-go-polyfill 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/buke/quickjs-go v0.4.15 7 | github.com/stretchr/testify v1.10.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/buke/quickjs-go v0.4.15 h1:VYs3OUaqoLzGXkXMqjh+MU2NVku883AlnMGwBr7sqpY= 2 | github.com/buke/quickjs-go v0.4.15/go.mod h1:DTNBNlQc+GflC2iemHKotAjdR5cKsfmkF75UuhA4iLs= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /inject.go: -------------------------------------------------------------------------------- 1 | package polyfill 2 | 3 | import ( 4 | "github.com/buke/quickjs-go" 5 | "github.com/buke/quickjs-go-polyfill/pkg/base64" 6 | "github.com/buke/quickjs-go-polyfill/pkg/console" 7 | "github.com/buke/quickjs-go-polyfill/pkg/fetch" 8 | "github.com/buke/quickjs-go-polyfill/pkg/window" 9 | ) 10 | 11 | func InjectAll(ctx *quickjs.Context) { 12 | window.InjectTo(ctx) 13 | fetch.InjectTo(ctx) 14 | console.InjectTo(ctx) 15 | base64.InjectTo(ctx) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/base64/base64.go: -------------------------------------------------------------------------------- 1 | package base64 2 | 3 | import ( 4 | stdBase64 "encoding/base64" 5 | 6 | "github.com/buke/quickjs-go" 7 | ) 8 | 9 | func atobFunc(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 10 | aStr := args[0].String() 11 | aByts, err := stdBase64.StdEncoding.DecodeString(aStr) 12 | if err != nil { 13 | return ctx.Error(err) 14 | } 15 | return ctx.String(string(aByts)) 16 | } 17 | 18 | func btoaFunc(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 19 | bStr := args[0].String() 20 | aByts := stdBase64.StdEncoding.EncodeToString([]byte(bStr)) 21 | return ctx.String(aByts) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/base64/base64_test.go: -------------------------------------------------------------------------------- 1 | package base64_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/buke/quickjs-go" 7 | "github.com/buke/quickjs-go-polyfill/pkg/base64" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestBase64(t *testing.T) { 12 | rt := quickjs.NewRuntime() 13 | defer rt.Close() 14 | 15 | ctx := rt.NewContext() 16 | defer ctx.Close() 17 | 18 | err := base64.InjectTo(ctx) 19 | require.NoError(t, err) 20 | 21 | retBtoa, err := ctx.Eval(`btoa("Hello World")`) 22 | defer retBtoa.Free() 23 | require.Equal(t, "SGVsbG8gV29ybGQ=", retBtoa.String()) 24 | 25 | retAtob, err := ctx.Eval(`atob("SGVsbG8gV29ybGQ=")`) 26 | defer retAtob.Free() 27 | require.Equal(t, "Hello World", retAtob.String()) 28 | 29 | uBtoa, err := ctx.Eval(`btoa("你好,世界")`) 30 | defer uBtoa.Free() 31 | require.Equal(t, "5L2g5aW977yM5LiW55WM", uBtoa.String()) 32 | 33 | uAtob, err := ctx.Eval(`atob("5L2g5aW977yM5LiW55WM")`) 34 | defer uAtob.Free() 35 | require.Equal(t, "你好,世界", uAtob.String()) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /pkg/base64/inject.go: -------------------------------------------------------------------------------- 1 | package base64 2 | 3 | import "github.com/buke/quickjs-go" 4 | 5 | func InjectTo(ctx *quickjs.Context) error { 6 | ctx.Globals().Set("atob", ctx.Function(atobFunc)) 7 | ctx.Globals().Set("btoa", ctx.Function(btoaFunc)) 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /pkg/console/console.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/buke/quickjs-go" 8 | ) 9 | 10 | func consoleFunc(fnType string) (Fn func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value) { 11 | switch fnType { 12 | case "trace", "debug", "info", "log", "warn", "error": 13 | return func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 14 | fn, _ := ctx.Eval(`(obj) => { 15 | return JSON.stringify(obj, (key, value) => { 16 | if(value instanceof Map) { 17 | return { 18 | dataType: 'Map', 19 | value: Array.from(value.entries()), // or with spread: value: [...value] 20 | }; 21 | } else { 22 | return value; 23 | } 24 | }); 25 | }`) 26 | defer fn.Free() 27 | 28 | for _, arg := range args { 29 | if arg.IsObject() { 30 | jsonStr := ctx.Invoke(fn, ctx.Null(), arg) 31 | defer jsonStr.Free() 32 | fmt.Fprintf(os.Stdout, "%s %s", arg.String(), jsonStr.String()) 33 | } else { 34 | fmt.Print(arg.String()) 35 | } 36 | fmt.Print(" ") 37 | } 38 | fmt.Println() 39 | return ctx.Null() 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/console/console_test.go: -------------------------------------------------------------------------------- 1 | package console_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/buke/quickjs-go" 7 | "github.com/buke/quickjs-go-polyfill/pkg/console" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestConsole(t *testing.T) { 12 | rt := quickjs.NewRuntime() 13 | defer rt.Close() 14 | 15 | ctx := rt.NewContext() 16 | defer ctx.Close() 17 | 18 | err := console.InjectTo(ctx) 19 | require.NoError(t, err) 20 | 21 | ret, err := ctx.Eval(` 22 | const obj = {"a": 1, "b": 2}; 23 | console.error(obj); 24 | 25 | const map = new Map(Object.entries({foo: 'bar'})) 26 | console.log(map); 27 | 28 | console.log('hello', 'world'); 29 | 30 | `) 31 | defer ret.Free() 32 | 33 | require.NoError(t, err) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /pkg/console/inject.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "github.com/buke/quickjs-go" 5 | ) 6 | 7 | func InjectTo(ctx *quickjs.Context) error { 8 | consoleObj := ctx.Object() 9 | consoleObj.Set("trace", ctx.Function(consoleFunc("trace"))) 10 | consoleObj.Set("debug", ctx.Function(consoleFunc("debug"))) 11 | consoleObj.Set("info", ctx.Function(consoleFunc("info"))) 12 | consoleObj.Set("log", ctx.Function(consoleFunc("log"))) 13 | consoleObj.Set("warn", ctx.Function(consoleFunc("warn"))) 14 | consoleObj.Set("error", ctx.Function(consoleFunc("error"))) 15 | 16 | ctx.Globals().Set("console", consoleObj) 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/fetch/fetch.go: -------------------------------------------------------------------------------- 1 | package fetch 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "time" 13 | 14 | "github.com/buke/quickjs-go" 15 | ) 16 | 17 | type QJSRequest struct { 18 | Body string `json:"body"` 19 | Headers map[string]string `json:"headers"` 20 | Method string `json:"method"` 21 | Redirect string `json:"redirect"` 22 | } 23 | 24 | type QJSResponse struct { 25 | Status int32 `json:"status"` 26 | StatusText string `json:"statusText"` 27 | OK bool `json:"ok"` 28 | Redirected bool `json:"redirected"` 29 | URL string `json:"url"` 30 | } 31 | 32 | func prepareReq(ctx *quickjs.Context, args []quickjs.Value) (*http.Request, error) { 33 | if len(args) <= 0 { 34 | return nil, errors.New("at lease 1 argument required") 35 | } 36 | rawURL := args[0].String() 37 | 38 | url, err := url.Parse(rawURL) 39 | if err != nil { 40 | return nil, fmt.Errorf("url '%s' is not valid", rawURL) 41 | } 42 | 43 | var jsReq QJSRequest 44 | if len(args) > 1 { 45 | if !args[1].IsObject() { 46 | return nil, errors.New("2nd argument must be an object") 47 | } 48 | reader := strings.NewReader(args[1].JSONStringify()) 49 | if err := json.NewDecoder(reader).Decode(&jsReq); err != nil { 50 | return nil, err 51 | } 52 | } 53 | 54 | if jsReq.Method == "" { 55 | jsReq.Method = "GET" 56 | } 57 | 58 | var body io.Reader 59 | if jsReq.Method != "GET" { 60 | body = strings.NewReader(jsReq.Body) 61 | } 62 | 63 | req, err := http.NewRequest(jsReq.Method, url.String(), body) 64 | if err != nil { 65 | return nil, err 66 | } 67 | for k, v := range jsReq.Headers { 68 | headerName := http.CanonicalHeaderKey(k) 69 | req.Header.Set(headerName, v) 70 | } 71 | 72 | if req.Header.Get("Accept") == "" { 73 | req.Header.Set("Accept", "*/*") 74 | } 75 | 76 | if req.Header.Get("Connection") == "" { 77 | req.Header.Set("Connection", "close") 78 | } 79 | 80 | req.Header.Set("Redirect", jsReq.Redirect) 81 | 82 | return req, nil 83 | } 84 | 85 | func fetch(req *http.Request) (*http.Response, error) { 86 | redirected := false 87 | client := &http.Client{ 88 | Transport: http.DefaultTransport, 89 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 90 | switch req.Header.Get("Redirect") { 91 | case "error": 92 | return errors.New("redirects are not allowed") 93 | default: 94 | if len(via) >= 10 { 95 | return errors.New("stopped after 10 redirects") 96 | } 97 | } 98 | 99 | redirected = true 100 | return nil 101 | }, 102 | Timeout: 30 * time.Second, 103 | } 104 | 105 | res, err := client.Do(req) 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | res.Header.Set("Redirected", fmt.Sprintf("%v", redirected)) 111 | 112 | return res, nil 113 | } 114 | 115 | func prepareResp(ctx *quickjs.Context, resp *http.Response) (quickjs.Value, error) { 116 | defer resp.Body.Close() 117 | respBody, err := ioutil.ReadAll(resp.Body) 118 | if err != nil { 119 | return ctx.Null(), err 120 | } 121 | 122 | // prepare response obj 123 | var jsResp QJSResponse 124 | jsResp.Status = int32(resp.StatusCode) 125 | jsResp.StatusText = resp.Status 126 | jsResp.OK = resp.StatusCode >= 200 && resp.StatusCode < 300 127 | jsResp.Redirected = resp.Header.Get("Redirected") == "true" 128 | jsResp.URL = resp.Request.URL.String() 129 | 130 | // marshal jsResp to json 131 | b, err := json.Marshal(jsResp) 132 | if err != nil { 133 | return ctx.Null(), err 134 | } 135 | 136 | // parse json to js object 137 | respObj := ctx.ParseJSON(string(b)) 138 | 139 | // header object 140 | headers := make(map[string]string) 141 | for k, v := range resp.Header { 142 | headers[k] = strings.Join(v, ",") 143 | } 144 | h, err := json.Marshal(headers) 145 | if err != nil { 146 | return ctx.Null(), err 147 | } 148 | // use js map to set header object 149 | fn, _ := ctx.Eval(`(str) => { 150 | return new Map(Object.entries(JSON.parse(str))); 151 | }`) 152 | defer fn.Free() 153 | 154 | headerObj := ctx.Invoke(fn, ctx.Null(), ctx.String(string(h))) 155 | if headerObj.IsError() { 156 | return ctx.Null(), headerObj.Error() 157 | } 158 | respObj.Set("headers", headerObj) 159 | 160 | respObj.Set("text", ctx.AsyncFunction(func(ctx *quickjs.Context, this quickjs.Value, promise quickjs.Value, args []quickjs.Value) quickjs.Value { 161 | return promise.Call("resolve", ctx.String(string(respBody))) 162 | })) 163 | 164 | respObj.Set("json", ctx.AsyncFunction(func(ctx *quickjs.Context, this quickjs.Value, promise quickjs.Value, args []quickjs.Value) quickjs.Value { 165 | retObj := ctx.ParseJSON(string(respBody)) 166 | defer retObj.Free() 167 | if retObj.IsError() { 168 | return promise.Call("reject", retObj) 169 | } 170 | 171 | return promise.Call("resolve", retObj) 172 | })) 173 | 174 | return respObj, nil 175 | 176 | } 177 | 178 | func fetchFunc(ctx *quickjs.Context, this quickjs.Value, promise quickjs.Value, args []quickjs.Value) quickjs.Value { 179 | // prepare request 180 | req, err := prepareReq(ctx, args) 181 | if err != nil { 182 | return promise.Call("reject", ctx.ThrowError(err)) 183 | } 184 | 185 | // fetch http request 186 | resp, err := fetch(req) 187 | if err != nil { 188 | return promise.Call("reject", ctx.ThrowError(err)) 189 | } 190 | 191 | // prepare response obj 192 | respObj, err := prepareResp(ctx, resp) 193 | if respObj.IsError() { 194 | fmt.Println(ctx.Exception()) 195 | } 196 | if err != nil { 197 | return promise.Call("reject", ctx.ThrowError(err)) 198 | } 199 | defer respObj.Free() 200 | 201 | return promise.Call("resolve", respObj) 202 | 203 | } 204 | -------------------------------------------------------------------------------- /pkg/fetch/fetch_test.go: -------------------------------------------------------------------------------- 1 | package fetch_test 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/buke/quickjs-go" 10 | "github.com/buke/quickjs-go-polyfill/pkg/fetch" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestFetch(t *testing.T) { 15 | 16 | rt := quickjs.NewRuntime() 17 | defer rt.Close() 18 | 19 | ctx := rt.NewContext() 20 | defer ctx.Close() 21 | 22 | fetch.InjectTo(ctx) 23 | 24 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | w.WriteHeader(http.StatusOK) 26 | w.Header().Set("Content-Type", "application/json; utf-8") 27 | _, _ = w.Write([]byte(`{"status": true}`)) 28 | })) 29 | 30 | val, _ := ctx.Eval(fmt.Sprintf(` 31 | var ret; 32 | fetch('%s', {Method: 'GET'}).then(response => response.json()).then(data => { 33 | ret = data.status; 34 | return data; 35 | })`, srv.URL)) 36 | defer val.Free() 37 | 38 | ctx.Loop() 39 | 40 | asyncRet, _ := ctx.Eval("ret") 41 | defer asyncRet.Free() 42 | 43 | require.EqualValues(t, true, asyncRet.Bool()) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /pkg/fetch/inject.go: -------------------------------------------------------------------------------- 1 | package fetch 2 | 3 | import ( 4 | "github.com/buke/quickjs-go" 5 | ) 6 | 7 | func InjectTo(ctx *quickjs.Context) error { 8 | ctx.Globals().Set("fetch", ctx.AsyncFunction(fetchFunc)) 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/window/inject.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | import ( 4 | "github.com/buke/quickjs-go" 5 | ) 6 | 7 | func InjectTo(ctx *quickjs.Context) error { 8 | ret, err := ctx.Eval(windowJs) 9 | defer ret.Free() 10 | 11 | if err != nil { 12 | return err 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/window/js/window.js: -------------------------------------------------------------------------------- 1 | globalThis.window = globalThis; -------------------------------------------------------------------------------- /pkg/window/window.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed js/window.js 8 | var windowJs string 9 | -------------------------------------------------------------------------------- /pkg/window/window_test.go: -------------------------------------------------------------------------------- 1 | package window_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/buke/quickjs-go" 7 | "github.com/buke/quickjs-go-polyfill/pkg/window" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestWindow(t *testing.T) { 12 | rt := quickjs.NewRuntime() 13 | defer rt.Close() 14 | 15 | ctx := rt.NewContext() 16 | defer ctx.Close() 17 | 18 | err := window.InjectTo(ctx) 19 | require.NoError(t, err) 20 | 21 | ret, _ := ctx.Eval("Object.is(globalThis,globalThis.window)") 22 | defer ret.Free() 23 | 24 | require.NoError(t, err) 25 | require.EqualValues(t, true, ret.Bool()) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /polyfill_test.go: -------------------------------------------------------------------------------- 1 | package polyfill_test 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | 8 | "github.com/buke/quickjs-go" 9 | polyfill "github.com/buke/quickjs-go-polyfill" 10 | ) 11 | 12 | func Example() { 13 | // Create a new runtime 14 | rt := quickjs.NewRuntime() 15 | defer rt.Close() 16 | 17 | // Create a new context 18 | ctx := rt.NewContext() 19 | defer ctx.Close() 20 | 21 | // Inject polyfills to the context 22 | polyfill.InjectAll(ctx) 23 | 24 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | w.WriteHeader(http.StatusOK) 26 | w.Header().Set("Content-Type", "application/json; utf-8") 27 | _, _ = w.Write([]byte(`{"status": true}`)) 28 | })) 29 | 30 | ret, _ := ctx.Eval(fmt.Sprintf(` 31 | setTimeout(() => { 32 | fetch('%s', {Method: 'GET'}).then(response => response.json()).then(data => { 33 | console.log(data.status); 34 | }); 35 | }, 50); 36 | `, srv.URL)) 37 | 38 | defer ret.Free() 39 | 40 | ctx.Loop() 41 | 42 | // Output: 43 | // true 44 | } 45 | --------------------------------------------------------------------------------