├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .prettierignore ├── .travis.yml ├── COMPARISON.md ├── LICENSE ├── Makefile ├── README.md ├── assets ├── logo.png └── logo.svg ├── bootstrap ├── include.go └── snap.go ├── bundler ├── bundle.go └── cache.go ├── cmd └── root.go ├── core ├── README.md ├── console.go ├── console_test.go ├── console_util.go ├── dispatch.go ├── ops.go ├── ops │ ├── env.go │ ├── fetch.go │ ├── fs.go │ └── serve.go ├── options │ └── options.go ├── plugins.go ├── plugins_win.go ├── repl.go └── run.go ├── dev ├── dev.go ├── report.go └── typescript.go ├── go.mod ├── go.sum ├── js ├── 00_core.js ├── 01_namespace.js ├── 02_console.js ├── 03_fetch.js ├── 04_event.js ├── 05_compiler.js ├── 06_encoder.js ├── 07_testing.js └── 10_window.js ├── main.go ├── mod.toml ├── module └── config.go ├── packager ├── exec.go ├── packager.go └── template.go ├── std ├── README.md ├── async │ ├── README.md │ ├── deferred.ts │ ├── deferred_test.ts │ ├── delay.ts │ ├── mux_async_iterator.ts │ └── pool.ts ├── bytes │ ├── README.md │ └── bytes.ts ├── encoding │ └── hex.ts ├── fmt │ └── colors.ts ├── fs │ └── exist.ts ├── hash │ ├── aes.ts │ ├── hasher.ts │ ├── md5.ts │ ├── sha1.ts │ ├── sha256.ts │ └── sha512.ts └── uuid │ ├── common.ts │ ├── v1.ts │ ├── v4.ts │ └── v5.ts ├── testing ├── bench │ ├── fs.js │ ├── fs_deno.js │ ├── fs_node.js │ ├── http_deno.ts │ ├── http_elsa.ts │ └── pi.js ├── bundle │ ├── basics.js │ ├── basics.js.mini.out │ ├── basics.js.out │ ├── export.js │ ├── hello.ts │ ├── hello.ts.out │ ├── local_imports.js │ ├── local_imports.js.out │ ├── served.ts │ └── url.ts ├── bundle_test.go ├── core_test.go ├── exports │ ├── 1.js │ ├── 2.js │ └── import.js ├── fs │ ├── cwd_test.js │ ├── exists_test.js │ ├── readFile_test.js │ ├── remove_test.js │ ├── sample.txt │ ├── stats_test.js │ └── writeFile_test.js ├── http │ └── serve.js ├── miscellaneous │ ├── args.js │ ├── args_test.js │ ├── mode_test.js │ └── while.js ├── ops │ ├── event.js │ └── pending_jobs.js ├── pkg_test.go ├── plugin │ ├── plugin.js │ └── test_plugin │ │ └── plugin.go ├── test_server.go ├── tests │ ├── mode_test.js │ ├── test1_test.js │ ├── test_test.js │ └── utils.ts ├── typescript │ ├── import.ts │ ├── typeerror.ts │ └── wrong.ts └── web │ ├── console_test.js │ ├── encode_test.js │ └── fetch_test.js ├── typescript ├── lib.es6.d.ts └── typescript.js └── util ├── check.go └── log.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ignore the bundled TypeScript source 2 | *.js linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build-mac-linux: 11 | name: Build ${{ matrix.os }} 12 | 13 | runs-on: ${{ matrix.os }} 14 | env: 15 | GOPATH: ${{ github.workspace }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest] 19 | 20 | steps: 21 | - name: Setup repo 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | path: "./src/github.com/elsaland/elsa" 26 | 27 | - name: Setup go 28 | uses: actions/setup-go@v2 29 | 30 | - name: Print go env 31 | run: go env 32 | 33 | - name: Install go-bindata 34 | run: go get github.com/go-bindata/go-bindata 35 | 36 | - name: Run bootstrap 37 | working-directory: ./src/github.com/elsaland/elsa 38 | run: go run ./bootstrap/ 39 | 40 | - name: Build 41 | working-directory: ./src/github.com/elsaland/elsa 42 | run: go build --ldflags "-s -w" -o elsa-${{ matrix.os }} . 43 | 44 | - name: Test 45 | working-directory: ./src/github.com/elsaland/elsa 46 | if: startsWith(matrix.os,'ubuntu') 47 | run: | 48 | go test ./testing 49 | ./elsa-${{ matrix.os }} test --fs --net 50 | 51 | - name: Upload Elsa executables 52 | uses: actions/upload-artifact@v2 53 | with: 54 | name: elsa-${{ matrix.os }} 55 | path: ./src/github.com/elsaland/elsa/elsa-${{ matrix.os }} 56 | 57 | build-windows: 58 | name: Build Windows 59 | 60 | runs-on: windows-latest 61 | env: 62 | GOPATH: ${{ github.workspace }} 63 | 64 | steps: 65 | - name: Setup repo 66 | uses: actions/checkout@v2 67 | with: 68 | submodules: true 69 | path: "./src/github.com/elsaland/elsa" 70 | 71 | - name: Setup go 72 | uses: actions/setup-go@v2 73 | 74 | - name: Print go env 75 | run: go env 76 | 77 | - name: Install go-bindata 78 | run: go get github.com/go-bindata/go-bindata 79 | 80 | - name: Run bootstrap 81 | working-directory: ./src/github.com/elsaland/elsa 82 | run: go run ./bootstrap/ 83 | 84 | - name: Build 85 | working-directory: ./src/github.com/elsaland/elsa 86 | run: go build --ldflags "-s -w" -o elsa-windows.exe . 87 | 88 | - name: Upload Elsa executables 89 | uses: actions/upload-artifact@v2 90 | with: 91 | name: elsa-windows-latest 92 | path: ./src/github.com/elsaland/elsa/elsa-windows.exe 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .vscode 3 | .idea 4 | 5 | # testing files 6 | output.js 7 | benchmarks 8 | test.ts 9 | 10 | # executable 11 | elsa 12 | # executable on windows 13 | elsa.exe 14 | 15 | # build cache 16 | data.go 17 | target 18 | 19 | # third-party tools 20 | node_modules 21 | 22 | # macOS specific files 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | typescript 2 | target 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | arch: 4 | - amd64 5 | - arm64 # please note arm64-graviton2 requires explicit virt: [lxd|vm] tag so it's recommended for jobs.include, see below 6 | os: linux # different CPU architectures are only supported on Linux 7 | 8 | env: 9 | - GO111MODULE=on 10 | 11 | go: 12 | - 1.14.6 13 | 14 | go_import_path: github.com/elsaland/elsa 15 | 16 | script: 17 | - go get github.com/go-bindata/go-bindata 18 | - make build 19 | -------------------------------------------------------------------------------- /COMPARISON.md: -------------------------------------------------------------------------------- 1 | ## Comparison 2 | 3 | | Features | Elsa (unreleased) | Deno v1.3.3 | Node.js v14.4.0 | 4 | | ------------------------ | ----------------- | ----------- | ---------------- | 5 | | Language | Go | Rust | C++ | 6 | | JS engine | **Quickjs** | V8 | V8 | 7 | | Bundler | Yes | Yes | No | 8 | | HTTP imports | Yes | Yes | No | 9 | | In-built package manager | No | No | Yes | 10 | | Compiling to executable | **Yes** | Yes | No (third party) | 11 | | Explicit imports | Yes | Yes | No | 12 | | Secure by default | Yes | Yes | No | 13 | | Binary size | **~12mb** \* | ~44mb \* | ~68mb | 14 | 15 | \* Lacks data for `Intl` and `toLocaleString` 16 | 17 | > The list is not complete and there is more to add, feel free to open a PR for the same. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 The elsa.land team 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go run ./bootstrap 3 | go build -o elsa . 4 | 5 | release: 6 | go run ./bootstrap 7 | go build --ldflags "-s -w" -o elsa . 8 | 9 | benchmark: 10 | # console benchmarks 11 | hyperfine './elsa run ./testing/web/console.js' 'deno run ./testing/web/console.js' 'node ./testing/console.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/console.json 12 | # bundle benchmarks 13 | hyperfine './elsa bundle ./testing/web/console.js' 'deno bundle ./testing/web/console.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/bundle.json -i 14 | # readFile benchmarks 15 | hyperfine './elsa run ./testing/bench/fs.js --fs' 'deno run --allow-read ./testing/bench/fs_deno.js' 'node ./testing/bench/fs_node.js' --warmup 100 -s full -r 100 --export-json ./benchmarks/fs.json 16 | # PI benchmarks 17 | hyperfine 'deno run testing/bench/pi.js' './elsa run testing/bench/pi.js' 'node testing/bench/pi.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/pi.json -i 18 | 19 | test: 20 | go test ./testing 21 | ./elsa test --fs --net 22 | 23 | test-create-out: 24 | ./elsa bundle testing/bundle/local_imports.js >> testing/bundle/local_imports.js.out 25 | ./elsa bundle testing/bundle/hello.ts >> testing/bundle/hello.ts.out 26 | ./elsa bundle testing/bundle/basics.js >> testing/bundle/basics.js.out 27 | 28 | clean-cache: 29 | rm -rf /tmp/x.nest.land/ 30 | rm -rf /tmp/deno.land/ 31 | 32 | fmt: 33 | gofmt -w . 34 | prettier --write . 35 | 36 | check-fmt: 37 | gofmt -l . 38 | prettier --check . 39 | 40 | minify-typescript: 41 | terser --compress --mangle -o ./typescript/typescript.js -- ./typescript/typescript.js 42 | 43 | .PHONY: build benchmark clean-cache fmt check-fmt 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Elsa 2 | 3 | [![Discord invite][]][discord invite link] 4 | 5 | 6 | 7 | Elsa is a _minimal_ JavaScript and TypeScript runtime written in Go. Built on top of quickjs and heavily inspired by Deno. 8 | 9 | ### Features 10 | 11 | - URL imports. 12 | - useful Web APIs. 13 | - compiles TypeScript out of the box. 14 | - bundling. `elsa bundle` 15 | - compiling to native distributable binaries. `elsa compile` 16 | 17 | ```typescript 18 | // hello.ts 19 | import { hello } from "https://x.nest.land/arweave-hello@0.0.2/mod.ts"; 20 | 21 | hello("Elsa"); 22 | ``` 23 | 24 | ```shell 25 | > elsa run hello.ts 26 | Hello, Elsa 27 | ``` 28 | 29 | [build status - badge]: https://github.com/elsaland/elsa/workflows/Build/badge.svg 30 | [build status]: https://github.com/elsaland/elsa/actions 31 | [discord invite]: https://img.shields.io/discord/757562931725467709?color=697EC4&label=Discord&logo=discord&logoColor=FDFEFE&style=flat-square 32 | [discord invite link]: https://discord.gg/Dw534ZY 33 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elsaland/elsa/1982c3d1cad0a8ed4160b52d68c7c941445a8fa5/assets/logo.png -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 40 | 42 | 44 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /bootstrap/include.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type ByNumericalFilename []os.FileInfo 10 | 11 | func (nf ByNumericalFilename) Len() int { return len(nf) } 12 | func (nf ByNumericalFilename) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] } 13 | func (nf ByNumericalFilename) Less(i, j int) bool { 14 | 15 | // Use path names 16 | pathA := nf[i].Name() 17 | pathB := nf[j].Name() 18 | 19 | // Grab integer value of each filename by parsing the string and slicing off 20 | // the extension 21 | a, err1 := strconv.ParseInt(pathA[0:strings.LastIndex(pathA, ".")], 10, 64) 22 | b, err2 := strconv.ParseInt(pathB[0:strings.LastIndex(pathB, ".")], 10, 64) 23 | 24 | // If any were not numbers sort lexographically 25 | if err1 != nil || err2 != nil { 26 | return pathA < pathB 27 | } 28 | 29 | // Which integer is smaller? 30 | return a < b 31 | } 32 | -------------------------------------------------------------------------------- /bootstrap/snap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "sort" 11 | 12 | "github.com/tdewolff/minify/v2" 13 | "github.com/tdewolff/minify/v2/js" 14 | ) 15 | 16 | func main() { 17 | files, _ := ioutil.ReadDir("js") 18 | 19 | sort.Sort(ByNumericalFilename(files)) 20 | 21 | m := minify.New() 22 | m.AddFunc("text/javascript", js.Minify) 23 | 24 | binCmd := []string{"run", "github.com/go-bindata/go-bindata/go-bindata", "-pkg", "core", "-o", "./core/data.go", "typescript/", "target/"} 25 | var finalSource string 26 | for _, f := range files { 27 | log.Printf("Bundling %s\n", f.Name()) 28 | 29 | file, err := os.Open(filepath.Join("js", f.Name())) 30 | if err != nil { 31 | log.Fatalf("Got error opening %s: %v", f.Name(), err) 32 | } 33 | 34 | buf := new(bytes.Buffer) 35 | if err := m.Minify("text/javascript", buf, file); err != nil { 36 | log.Fatalf("Got error minifying %s: %v", f.Name(), err) 37 | } 38 | 39 | finalSource += buf.String() + "\n" 40 | } 41 | err := os.Mkdir("target", 0750) 42 | if err != nil { 43 | log.Fatalf("Error in making directory - %v", err) 44 | } 45 | err = ioutil.WriteFile(filepath.Join("target", "elsa.js"), []byte(finalSource), 0644) 46 | if err != nil { 47 | log.Fatalf("Error writing file %v", err) 48 | } 49 | cmd := exec.Command("go", binCmd...) 50 | log.Printf("Running command and waiting for it to finish...") 51 | err = cmd.Run() 52 | log.Printf("Command finished with error: %v", err) 53 | } 54 | -------------------------------------------------------------------------------- /bundler/bundle.go: -------------------------------------------------------------------------------- 1 | package bundler 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | 12 | "github.com/elsaland/elsa/module" 13 | "github.com/elsaland/elsa/util" 14 | 15 | "github.com/asaskevich/govalidator" 16 | "github.com/evanw/esbuild/pkg/api" 17 | ) 18 | 19 | var cache = ElsaCache{os.TempDir()} 20 | 21 | func BundleModule(file string, minify bool, config *module.Config) string { 22 | tsconfig := "" 23 | dir := filepath.Dir(file) 24 | tsconfigPath := path.Join(dir, "tsconfig.json") 25 | if _, err := os.Stat(tsconfigPath); err == nil || !os.IsNotExist(err) { 26 | tsconfig = tsconfigPath 27 | } 28 | bundle := api.Build(api.BuildOptions{ 29 | EntryPoints: []string{file}, 30 | Outfile: "output.js", 31 | Bundle: true, 32 | Target: api.ESNext, 33 | LogLevel: api.LogLevelInfo, 34 | MinifyIdentifiers: minify, 35 | MinifySyntax: minify, 36 | MinifyWhitespace: minify, 37 | Tsconfig: tsconfig, 38 | Plugins: []func(api.Plugin){ 39 | 40 | func(plugin api.Plugin) { 41 | plugin.SetName("url-loader") 42 | plugin.AddResolver(api.ResolverOptions{Filter: "^https?://"}, 43 | func(args api.ResolverArgs) (api.ResolverResult, error) { 44 | possibleCachePath := cache.UrlToPath(args.Path) 45 | if cache.InCache(possibleCachePath) && cache.Exists(possibleCachePath) { 46 | return api.ResolverResult{Path: possibleCachePath, Namespace: ""}, nil 47 | } 48 | // Get the data 49 | f := BundleURL(args.Path, minify) 50 | return api.ResolverResult{Path: f, Namespace: ""}, nil 51 | 52 | }) 53 | }, 54 | }, 55 | }) 56 | if bundle.Errors != nil { 57 | os.Exit(1) 58 | } 59 | return string(bundle.OutputFiles[0].Contents) 60 | } 61 | 62 | func BundleURL(uri string, minify bool) string { 63 | resp, _ := http.Get(uri) 64 | fileName := cache.BuildFileName(uri) 65 | util.LogInfo("Downloading", fmt.Sprintf("%s => %s", uri, fileName)) 66 | defer resp.Body.Close() 67 | file, err := cache.Create(fileName) 68 | if err != nil { 69 | util.LogError("Internal", fmt.Sprintf("%s", err)) 70 | os.Exit(1) 71 | } 72 | io.Copy(file, resp.Body) 73 | defer file.Close() 74 | bundle := api.Build(api.BuildOptions{ 75 | EntryPoints: []string{file.Name()}, 76 | Outfile: "output.js", 77 | Bundle: true, 78 | Target: api.ES2015, 79 | LogLevel: api.LogLevelInfo, 80 | MinifyIdentifiers: minify, 81 | MinifyWhitespace: minify, 82 | MinifySyntax: minify, 83 | Plugins: []func(api.Plugin){ 84 | 85 | func(plugin api.Plugin) { 86 | plugin.SetName("url-loader2") 87 | plugin.AddResolver(api.ResolverOptions{Filter: ".*?"}, 88 | func(args api.ResolverArgs) (api.ResolverResult, error) { 89 | dir := filepath.Dir(file.Name()) 90 | possibleCachePath := path.Join(dir, args.Path) 91 | if cache.InCache(possibleCachePath) && cache.Exists(possibleCachePath) { 92 | return api.ResolverResult{Path: possibleCachePath, Namespace: ""}, nil 93 | } 94 | if govalidator.IsURL(args.Path) { 95 | uri = args.Path 96 | cha := make(chan string) 97 | go func(url string, u chan string) { 98 | u <- BundleURL(url, minify) 99 | }(args.Path, cha) 100 | bundle := <-cha 101 | return api.ResolverResult{Path: bundle, Namespace: ""}, nil 102 | } 103 | base, err := url.Parse(uri) 104 | if err != nil { 105 | panic(err) 106 | } 107 | pth, err := url.Parse(args.Path) 108 | if err != nil { 109 | panic(err) 110 | } 111 | loc := base.ResolveReference(pth).String() 112 | fileName := cache.BuildFileName(loc) 113 | util.LogInfo("Downloading", fmt.Sprintf("%s => %s", loc, file.Name())) 114 | // Get the data 115 | resp, _ := http.Get(loc) 116 | defer resp.Body.Close() 117 | file, err := cache.Create(fileName) 118 | if err != nil { 119 | util.LogError("Internal", fmt.Sprintf("%s", err)) 120 | os.Exit(1) 121 | } 122 | _, err = io.Copy(file, resp.Body) 123 | if err != nil { 124 | util.LogError("Internal", fmt.Sprintf("%s", err)) 125 | os.Exit(1) 126 | } 127 | 128 | defer file.Close() 129 | 130 | return api.ResolverResult{Path: file.Name(), Namespace: ""}, nil 131 | 132 | }) 133 | }, 134 | }, 135 | }) 136 | if bundle.Errors != nil { 137 | os.Exit(1) 138 | } 139 | return file.Name() 140 | } 141 | -------------------------------------------------------------------------------- /bundler/cache.go: -------------------------------------------------------------------------------- 1 | package bundler 2 | 3 | import ( 4 | "net/url" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | type ElsaCache struct { 12 | dir string 13 | } 14 | 15 | func (cache *ElsaCache) BuildFileName(uri string) string { 16 | fileUrl, _ := url.Parse(uri) 17 | path := path.Join(cache.dir, fileUrl.Host, fileUrl.Path) 18 | return path 19 | } 20 | 21 | func (cache *ElsaCache) PathToUrl(path string) string { 22 | parts := strings.Split(path, "/")[2:] 23 | url, _ := url.Parse("https://" + strings.Join(parts, "/")) 24 | return url.String() 25 | } 26 | 27 | func (cache *ElsaCache) UrlToPath(url string) string { 28 | parts := strings.Split(url, "//")[1] 29 | path := path.Join(cache.dir, parts) 30 | return path 31 | } 32 | 33 | func (cache *ElsaCache) InCache(path string) bool { 34 | return strings.HasPrefix(path, cache.dir) 35 | } 36 | 37 | func (cache *ElsaCache) Exists(path string) bool { 38 | if _, err := os.Stat(path); os.IsNotExist(err) { 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | func (cache *ElsaCache) Create(p string) (*os.File, error) { 45 | if err := os.MkdirAll(filepath.Dir(p), 0750); err != nil { 46 | return nil, err 47 | } 48 | return os.Create(p) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "runtime" 11 | 12 | "github.com/elsaland/elsa/core" 13 | "github.com/elsaland/elsa/core/options" 14 | "github.com/elsaland/elsa/module" 15 | "github.com/elsaland/elsa/packager" 16 | "github.com/elsaland/elsa/util" 17 | "github.com/fatih/color" 18 | "github.com/mitchellh/go-homedir" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var homeDir, _ = homedir.Dir() 23 | 24 | var installSite = path.Join(homeDir, "./.elsa/") 25 | 26 | // Elsa functions expected to be passed into cmd 27 | type Elsa struct { 28 | Run func(opt options.Options) 29 | Dev func(og string, opt options.Options) 30 | Bundle func(file string, minify bool, config *module.Config) string 31 | } 32 | 33 | // Execute start the CLI 34 | func Execute(elsa Elsa) { 35 | // TODO: need to come to a concrete conclusion 36 | // Load mod.toml (if exists) 37 | config, err := module.ConfigLoad() 38 | util.Check(err) 39 | 40 | color.NoColor = config.Options.NoColor 41 | 42 | var fsFlag bool 43 | var netFlag bool 44 | var minifyFlag bool 45 | var envFlag bool 46 | var installName string 47 | 48 | // Root command 49 | var rootCmd = &cobra.Command{ 50 | Use: "", 51 | Short: "", 52 | Run: func(cmd *cobra.Command, args []string) { 53 | core.Repl() 54 | }, 55 | } 56 | 57 | // Run subcommand 58 | var runCmd = &cobra.Command{ 59 | Use: "run [file]", 60 | Short: "Run a JavaScript and TypeScript source file", 61 | Args: cobra.MinimumNArgs(1), 62 | Run: func(cmd *cobra.Command, args []string) { 63 | if len(args) >= 0 { 64 | bundle := elsa.Bundle(args[0], true, config) 65 | env := options.Environment{ 66 | NoColor: config.Options.NoColor, 67 | Args: args[1:], 68 | RunTests: false, 69 | } 70 | opt := options.Options{ 71 | SourceFile: args[0], 72 | Source: bundle, 73 | Perms: &options.Perms{fsFlag, netFlag, envFlag}, 74 | Env: env, 75 | } 76 | elsa.Run(opt) 77 | } 78 | }, 79 | } 80 | 81 | // --fs,--net, --env flags 82 | runCmd.Flags().BoolVar(&fsFlag, "fs", false, "Allow file system access") 83 | runCmd.Flags().BoolVar(&netFlag, "net", false, "Allow net access") 84 | runCmd.Flags().BoolVar(&envFlag, "env", false, "Allow Environment Variables access") 85 | 86 | // dev subcommand to run script in development mode 87 | var devCmd = &cobra.Command{ 88 | Use: "dev [file]", 89 | Short: "Run a script in development mode.", 90 | Long: `Run a script in development mode. It enables type-checking using the inbuilt TypeScript compiler.`, 91 | Args: cobra.MinimumNArgs(1), 92 | Run: func(cmd *cobra.Command, args []string) { 93 | if len(args) >= 0 { 94 | bundle := elsa.Bundle(args[0], true, config) 95 | env := options.Environment{ 96 | NoColor: config.Options.NoColor, 97 | Args: args[1:], 98 | RunTests: false, 99 | } 100 | opt := options.Options{ 101 | SourceFile: args[0], 102 | Source: bundle, 103 | Perms: &options.Perms{ 104 | Fs: true, 105 | Env: true, 106 | Net: true, 107 | }, 108 | Env: env, 109 | } 110 | og, _ := ioutil.ReadFile(args[0]) 111 | elsa.Dev(string(og), opt) 112 | } 113 | }, 114 | } 115 | 116 | // bundle subcommand to bundle a source file 117 | var bundleCmd = &cobra.Command{ 118 | Use: "bundle [file]", 119 | Short: "Bundle your script to a single JavaScript file", 120 | Long: `Bundle your script to a single JavaScript file. It utilizes esbuild for super fast bundling.`, 121 | Args: cobra.MinimumNArgs(1), 122 | Run: func(cmd *cobra.Command, args []string) { 123 | if len(args) >= 0 { 124 | out := elsa.Bundle(args[0], minifyFlag, config) 125 | fmt.Println(out) 126 | } 127 | }, 128 | } 129 | 130 | // --minify flag for bundling 131 | bundleCmd.Flags().BoolVarP(&minifyFlag, "minify", "m", false, "Minify the output bundle") 132 | 133 | // pkg subcommand for trigger the packager 134 | var pkgCmd = &cobra.Command{ 135 | Use: "pkg [file]", 136 | Short: "Package your script to a standalone executable.", 137 | Long: `Package your script to a standalone executable.`, 138 | Args: cobra.MinimumNArgs(1), 139 | Run: func(cmd *cobra.Command, args []string) { 140 | if len(args) >= 0 { 141 | packager.PkgSource(args[0]) 142 | } 143 | }, 144 | } 145 | 146 | // test subcommand to run test files 147 | var testCmd = &cobra.Command{ 148 | Use: "test", 149 | Short: "Run tests for your Elsa scripts.", 150 | Long: `Run tests for your Elsa scripts. All files matching *_test.js are run.`, 151 | Run: func(cmd *cobra.Command, args []string) { 152 | env := options.Environment{ 153 | NoColor: config.Options.NoColor, 154 | Args: args, 155 | RunTests: true, 156 | } 157 | opt := options.Options{ 158 | Perms: &options.Perms{fsFlag, netFlag, envFlag}, 159 | Env: env, 160 | } 161 | tests := CollectTests() 162 | for _, test := range tests { 163 | opt.SourceFile = test 164 | bundle := elsa.Bundle(test, true, config) 165 | opt.Source = bundle 166 | elsa.Run(opt) 167 | } 168 | }, 169 | } 170 | 171 | // --net, --fs, --env perms 172 | testCmd.Flags().BoolVar(&fsFlag, "fs", false, "Allow file system access") 173 | testCmd.Flags().BoolVar(&netFlag, "net", false, "Allow net access") 174 | testCmd.Flags().BoolVar(&envFlag, "env", false, "Allow Environment Variables access") 175 | 176 | // install subcommand to bundle and shebang to PATH env 177 | var installCmd = &cobra.Command{ 178 | Use: "install", 179 | Short: "Install an Elsa module.", 180 | Long: `Install an Elsa module.`, 181 | Run: func(cmd *cobra.Command, args []string) { 182 | if len(args) >= 0 { 183 | out := elsa.Bundle(args[0], true, config) 184 | bundleLoc := path.Join(os.TempDir(), installName+".js") 185 | err := ioutil.WriteFile(bundleLoc, []byte(out), 0777) 186 | if err != nil { 187 | panic(err) 188 | } 189 | scriptFile := path.Join(installSite, installName) 190 | 191 | // add .cmd in script for windows 192 | isWindows(&scriptFile, scriptFile+".cmd") 193 | err = ioutil.WriteFile(scriptFile, []byte(shebang(bundleLoc)), 0777) 194 | if err != nil { 195 | panic(err) 196 | } 197 | fmt.Println("Installation complete.") 198 | } 199 | }, 200 | } 201 | installCmd.Flags().StringVar(&installName, "name", "00", "Executable name of the installed script") 202 | // Add subcommands to root command 203 | rootCmd.AddCommand(bundleCmd, runCmd, pkgCmd, devCmd, testCmd, installCmd) 204 | 205 | // Execute! :) 206 | if err := rootCmd.Execute(); err != nil { 207 | fmt.Println(err) 208 | os.Exit(1) 209 | } 210 | } 211 | 212 | func shebang(loc string) string { 213 | exec := ` 214 | #!/bin/sh 215 | elsa "run" "%s" "$@"` 216 | // for windows 217 | isWindows(&exec, `@elsa "run" "%s" %*`) 218 | 219 | return fmt.Sprintf(exec, loc) 220 | } 221 | 222 | // replace a string if it is windows 223 | func isWindows(toChange *string, replacement string) { 224 | if runtime.GOOS == "windows" { 225 | *toChange = replacement 226 | } 227 | } 228 | 229 | // match test files 230 | func matchedFiles(name string) bool { 231 | matchedJS, err := filepath.Match("*_test.js", name) 232 | matchedTS, err := filepath.Match("*_test.ts", name) 233 | matchedJSTest, err := filepath.Match("*.test.js", name) 234 | matchedTSTest, err := filepath.Match("*.test.ts", name) 235 | 236 | if err != nil { 237 | log.Fatal(err) 238 | } 239 | 240 | return (matchedJS || matchedTS || matchedTSTest || matchedJSTest) 241 | } 242 | 243 | // CollectTests files 244 | func CollectTests() []string { 245 | var testFiles []string 246 | e := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 247 | if err == nil { 248 | if err != nil { 249 | return nil 250 | } 251 | if matchedFiles(info.Name()) { 252 | testFiles = append(testFiles, path) 253 | } 254 | } 255 | return nil 256 | }) 257 | if e != nil { 258 | log.Fatal(e) 259 | } 260 | return testFiles 261 | } 262 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | ## `core` 2 | 3 | The directory contains code for Elsa's runtime agnostic. 4 | It comes with a simple Go API for developers to embed Elsa applications into their Go code. 5 | 6 | ### Usage 7 | 8 | ```go 9 | package main 10 | 11 | import ( 12 | "github.com/elsaland/elsa/core" 13 | "github.com/elsaland/elsa/core/options" 14 | ) 15 | 16 | var source string: = "console.log(1)" 17 | 18 | func main() { 19 | env: = options.Environment { 20 | NoColor: false, // Set true to disable coloured output 21 | Args: os.Args[1: ], 22 | } 23 | opt: = options.Options { 24 | SourceFile: "file.js", 25 | Source: source, 26 | Perms: & options.Perms { 27 | Fs: false, // Set true to allow file system access 28 | }, 29 | Env: env, 30 | } 31 | core.Run(opt) 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /core/console.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/elsaland/quickjs" 8 | "github.com/fatih/color" 9 | ) 10 | 11 | // Create a new console formatter from console_util.go 12 | var f = NewFormatter() 13 | 14 | // ConsoleLog console.log bindings to quickjs engine 15 | func ConsoleLog(ctx *quickjs.Context, value []quickjs.Value) quickjs.Value { 16 | data := value[2] 17 | // dataType is the JavaScript type of the data => `typeof arg` 18 | dataType := value[1].String() 19 | var result interface{} 20 | switch dataType { 21 | case "string": 22 | // Prints a string (without color) 23 | fmt.Println(data.String()) 24 | case "function": 25 | // Prints String(myFunction) 26 | fmt.Fprintln(color.Output, color.New(color.FgCyan).SprintFunc()(data.String())) 27 | case "bigint": 28 | // Prints bigint corresponding to number 29 | fmt.Fprintln(color.Output, color.New(color.FgYellow).SprintFunc()(data.BigInt())) 30 | case "number": 31 | // Prints a number 32 | fmt.Fprintln(color.Output, color.New(color.FgYellow).SprintFunc()(data.Int32())) 33 | default: 34 | // Hands over the data as string to console util for parsing arrays and objects 35 | json.Unmarshal([]byte(data.String()), &result) 36 | prty, _ := f.Marshal(result) 37 | // Prints the formatted result 38 | fmt.Fprintln(color.Output, string(prty)) 39 | } 40 | 41 | return ctx.Null() 42 | } 43 | -------------------------------------------------------------------------------- /core/console_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/elsaland/elsa/util" 7 | "testing" 8 | ) 9 | 10 | const testString = `"This is a string"` 11 | const testArray = `["string", 100, {}]` 12 | const testNumber = `120` 13 | const testDiverseJSON = `{ 14 | "str": "foo", 15 | "num": 100, 16 | "bool": false, 17 | "null": null, 18 | "array": ["foo", "bar", "baz"], 19 | "obj": { "a": 1, "b": 2 } 20 | }` 21 | 22 | func expectPass(str string, t *testing.T) { 23 | var result interface{} 24 | err := json.Unmarshal([]byte(str), &result) 25 | util.Check(err) 26 | prty, err := Marshal(result) 27 | util.Check(err) 28 | fmt.Println(string(prty)) 29 | } 30 | 31 | func TestString(t *testing.T) { expectPass(testString, t) } 32 | func TestDiverseJSON(t *testing.T) { expectPass(testDiverseJSON, t) } 33 | func TestNumber(t *testing.T) { expectPass(testNumber, t) } 34 | func TestArray(t *testing.T) { expectPass(testArray, t) } 35 | -------------------------------------------------------------------------------- /core/console_util.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | 6 | "bytes" 7 | "encoding/json" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/fatih/color" 13 | ) 14 | 15 | const initialDepth = 0 16 | const valueSep = "," 17 | const null = "null" 18 | const startMap = "{" 19 | const endMap = "}" 20 | const startArray = "[" 21 | const endArray = "]" 22 | 23 | const emptyMap = startMap + endMap 24 | const emptyArray = startArray + endArray 25 | 26 | type Formatter struct { 27 | KeyColor *color.Color 28 | StringColor *color.Color 29 | BoolColor *color.Color 30 | NumberColor *color.Color 31 | NullColor *color.Color 32 | StringMaxLength int 33 | Indent int 34 | DisabledColor bool 35 | RawStrings bool 36 | } 37 | 38 | func NewFormatter() *Formatter { 39 | return &Formatter{ 40 | KeyColor: color.New(color.FgWhite), 41 | StringColor: color.New(color.FgGreen), 42 | BoolColor: color.New(color.FgYellow), 43 | NumberColor: color.New(color.FgCyan), 44 | NullColor: color.New(color.FgMagenta), 45 | StringMaxLength: 0, 46 | DisabledColor: false, 47 | Indent: 0, 48 | RawStrings: false, 49 | } 50 | } 51 | 52 | func (f *Formatter) sprintfColor(c *color.Color, format string, args ...interface{}) string { 53 | if f.DisabledColor || c == nil { 54 | return fmt.Sprintf(format, args...) 55 | } 56 | return c.SprintfFunc()(format, args...) 57 | } 58 | 59 | func (f *Formatter) sprintColor(c *color.Color, s string) string { 60 | if f.DisabledColor || c == nil { 61 | return fmt.Sprint(s) 62 | } 63 | return c.SprintFunc()(s) 64 | } 65 | 66 | func (f *Formatter) writeIndent(buf *bytes.Buffer, depth int) { 67 | buf.WriteString(strings.Repeat(" ", f.Indent*depth)) 68 | } 69 | 70 | func (f *Formatter) writeObjSep(buf *bytes.Buffer) { 71 | if f.Indent != 0 { 72 | buf.WriteByte('\n') 73 | } else { 74 | buf.WriteByte(' ') 75 | } 76 | } 77 | 78 | func (f *Formatter) Marshal(jsonObj interface{}) ([]byte, error) { 79 | buffer := bytes.Buffer{} 80 | f.marshalValue(jsonObj, &buffer, initialDepth) 81 | return buffer.Bytes(), nil 82 | } 83 | 84 | func (f *Formatter) marshalMap(m map[string]interface{}, buf *bytes.Buffer, depth int) { 85 | remaining := len(m) 86 | 87 | if remaining == 0 { 88 | buf.WriteString(emptyMap) 89 | return 90 | } 91 | 92 | keys := make([]string, 0) 93 | for key := range m { 94 | keys = append(keys, key) 95 | } 96 | 97 | sort.Strings(keys) 98 | 99 | buf.WriteString(startMap) 100 | f.writeObjSep(buf) 101 | 102 | for _, key := range keys { 103 | f.writeIndent(buf, depth+1) 104 | buf.WriteString(f.KeyColor.Sprintf("\"%s\": ", key)) 105 | f.marshalValue(m[key], buf, depth+1) 106 | remaining-- 107 | if remaining != 0 { 108 | buf.WriteString(valueSep) 109 | } 110 | f.writeObjSep(buf) 111 | } 112 | f.writeIndent(buf, depth) 113 | buf.WriteString(endMap) 114 | } 115 | 116 | func (f *Formatter) marshalArray(a []interface{}, buf *bytes.Buffer, depth int) { 117 | if len(a) == 0 { 118 | buf.WriteString(emptyArray) 119 | return 120 | } 121 | 122 | buf.WriteString(startArray) 123 | f.writeObjSep(buf) 124 | 125 | for i, v := range a { 126 | f.writeIndent(buf, depth+1) 127 | f.marshalValue(v, buf, depth+1) 128 | if i < len(a)-1 { 129 | buf.WriteString(valueSep) 130 | } 131 | f.writeObjSep(buf) 132 | } 133 | f.writeIndent(buf, depth) 134 | buf.WriteString(endArray) 135 | } 136 | 137 | func (f *Formatter) marshalValue(val interface{}, buf *bytes.Buffer, depth int) { 138 | switch v := val.(type) { 139 | case map[string]interface{}: 140 | f.marshalMap(v, buf, depth) 141 | case []interface{}: 142 | f.marshalArray(v, buf, depth) 143 | case string: 144 | f.marshalString(v, buf) 145 | case float64: 146 | buf.WriteString(f.sprintColor(f.NumberColor, strconv.FormatFloat(v, 'f', -1, 64))) 147 | case bool: 148 | buf.WriteString(f.sprintColor(f.BoolColor, (strconv.FormatBool(v)))) 149 | case nil: 150 | buf.WriteString(f.sprintColor(f.NullColor, null)) 151 | case json.Number: 152 | buf.WriteString(f.sprintColor(f.NumberColor, v.String())) 153 | } 154 | } 155 | 156 | func (f *Formatter) marshalString(str string, buf *bytes.Buffer) { 157 | if !f.RawStrings { 158 | strBytes, _ := json.Marshal(str) 159 | str = string(strBytes) 160 | } 161 | 162 | if f.StringMaxLength != 0 && len(str) >= f.StringMaxLength { 163 | str = fmt.Sprintf("%s...", str[0:f.StringMaxLength]) 164 | } 165 | 166 | buf.WriteString(f.sprintColor(f.StringColor, str)) 167 | } 168 | 169 | // Marshal JSON data with default options 170 | func Marshal(jsonObj interface{}) ([]byte, error) { 171 | return NewFormatter().Marshal(jsonObj) 172 | } 173 | -------------------------------------------------------------------------------- /core/dispatch.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/elsaland/elsa/util" 8 | 9 | "github.com/elsaland/elsa/core/ops" 10 | "github.com/elsaland/elsa/core/options" 11 | "github.com/elsaland/quickjs" 12 | "github.com/spf13/afero" 13 | ) 14 | 15 | // ElsaSendNS Native function corresponding to the JavaScript global `__send` 16 | // It is binded with `__send` and accepts arguments including op ID 17 | func ElsaSendNS(elsa *options.Elsa) func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 18 | // Create a new file system driver 19 | var fs = ops.FsDriver{ 20 | // NOTE: afero can also be used to create in-memory file system 21 | // it can be a feature to provide in the future 22 | Fs: afero.NewOsFs(), 23 | Perms: elsa.Perms, 24 | } 25 | // The returned function handles the op and execute corresponding native code 26 | return func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 27 | switch args[0].Int32() { 28 | case FSRead: 29 | CheckFs(elsa.Perms) 30 | file := args[1] 31 | val := fs.ReadFile(ctx, file) 32 | return val 33 | case FSExists: 34 | CheckFs(elsa.Perms) 35 | file := args[1] 36 | val := fs.Exists(ctx, file) 37 | return val 38 | case FSWrite: 39 | CheckFs(elsa.Perms) 40 | file := args[1] 41 | contents := args[2] 42 | val := fs.WriteFile(ctx, file, contents) 43 | return val 44 | case FSCwd: 45 | CheckFs(elsa.Perms) 46 | val := fs.Cwd(ctx) 47 | return val 48 | case FSStat: 49 | CheckFs(elsa.Perms) 50 | file := args[1] 51 | val := fs.Stat(ctx, file) 52 | return val 53 | case FSRemove: 54 | CheckFs(elsa.Perms) 55 | file := args[1] 56 | val := fs.Remove(ctx, file) 57 | return val 58 | case Log: 59 | return ConsoleLog(ctx, args) 60 | case Plugin: 61 | plugin := args[1].String() 62 | input := args[2].String() 63 | dat := (OpenPlugin(plugin, input)).(string) 64 | val := ctx.String(dat) 65 | defer val.Free() 66 | return val 67 | case Fetch: 68 | CheckNet(elsa.Perms) 69 | one := args[1] 70 | url := args[2] 71 | body := ops.Fetch(ctx, url) 72 | obj := ctx.Object() 73 | defer obj.Free() 74 | obj.Set("ok", body) 75 | elsa.Recv(one, obj) 76 | return ctx.Null() 77 | case Serve: 78 | id := args[1] 79 | url := args[2] 80 | cb := func(res quickjs.Value) string { 81 | obj := ctx.Object() 82 | defer obj.Free() 83 | obj.Set("ok", res) 84 | rtrn := elsa.Recv(id, res) 85 | return rtrn.String() 86 | } 87 | ops.Serve(ctx, cb, id, url) 88 | return ctx.Null() 89 | case FSMkdir: 90 | CheckFs(elsa.Perms) 91 | file := args[1] 92 | val := fs.Mkdir(ctx, file) 93 | return val 94 | case Env: 95 | CheckEnv(elsa.Perms) 96 | val := ops.Env(ctx, args) 97 | return val 98 | case FSWalk: 99 | CheckFs(elsa.Perms) 100 | file := args[1] 101 | val := fs.Walk(ctx, file) 102 | return val 103 | default: 104 | return ctx.Null() 105 | } 106 | } 107 | } 108 | 109 | // CheckFs utility to check whether file system access is avaliable or not 110 | func CheckFs(perms *options.Perms) { 111 | if !perms.Fs { 112 | util.LogError("Perms Error: ", "Filesystem access is blocked.") 113 | os.Exit(1) 114 | } 115 | } 116 | 117 | // CheckNet utility to check whether net access is avaliable or not 118 | func CheckNet(perms *options.Perms) { 119 | if !perms.Net { 120 | util.LogError("Perms Error: ", "Net is blocked.") 121 | os.Exit(1) 122 | } 123 | } 124 | 125 | func CheckEnv(perms *options.Perms) { 126 | if !perms.Env { 127 | util.LogError("Perms Error: ", "Environment Variables is blocked.") 128 | os.Exit(1) 129 | } 130 | } 131 | 132 | // ElsaRecvNS Native function corresponding to the JavaScript global `__recv` 133 | // It is binded with `__recv` and accepts arguments including recv ID of the async function 134 | func ElsaRecvNS(elsa *options.Elsa) func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 135 | // the returned function handles the __recv behaviour 136 | // It is capable of calling the callback for a particular async op after it has finished 137 | return func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 138 | fn := args[0] 139 | if elsa.Recv != nil { 140 | ctx.ThrowError(fmt.Errorf("recv cannot be called more than once")) 141 | return ctx.Null() 142 | } 143 | elsa.Recv = func(id quickjs.Value, val quickjs.Value) quickjs.Value { 144 | result := fn.Call(id, val) 145 | // defer result.Free() 146 | return result 147 | } 148 | return ctx.Null() 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /core/ops.go: -------------------------------------------------------------------------------- 1 | // Package core lists all the OPS in elsa 2 | // Will be updated as an when new OPS are added 3 | package core 4 | 5 | // FileSystem ops 6 | const ( 7 | FSWrite = 1 8 | FSRead = 2 9 | FSExists = 3 10 | FSDirExists = 4 11 | FSCwd = 5 12 | Serve = 25 13 | FSStat = 6 14 | FSRemove = 7 15 | FSMkdir = 9 16 | FSWalk = 14 17 | ) 18 | 19 | // console binding ops 20 | const ( 21 | Log = 10 22 | ) 23 | 24 | // plugin ops 25 | const ( 26 | Plugin = 15 27 | ) 28 | 29 | // fetch ops 30 | const ( 31 | Fetch = 20 32 | ) 33 | 34 | // env ops 35 | const ( 36 | Env = 11 37 | ) 38 | -------------------------------------------------------------------------------- /core/ops/env.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strings" 7 | 8 | "github.com/elsaland/elsa/util" 9 | "github.com/elsaland/quickjs" 10 | ) 11 | 12 | // Env elsa handler 13 | func Env(ctx *quickjs.Context, data []quickjs.Value) quickjs.Value { 14 | 15 | // Elsa.env.get 16 | if len(data) == 2 && data[1].IsString() { 17 | key := os.Getenv(data[1].String()) 18 | return ctx.String(key) 19 | } 20 | 21 | // Elsa.env.set 22 | if len(data) == 3 && (data[1].IsString() && data[2].IsString()) { 23 | err := os.Setenv(data[1].String(), data[2].String()) 24 | util.Check(err) 25 | 26 | return ctx.Null() 27 | } 28 | 29 | // Elsa.env.toObject 30 | if len(data) == 2 && data[1].IsBool() { 31 | getenvironment := func(envs []string, getkeyval func(item string) (key, val string)) map[string]string { 32 | items := make(map[string]string) 33 | for _, item := range envs { 34 | key, val := getkeyval(item) 35 | items[key] = val 36 | } 37 | return items 38 | } 39 | 40 | // get envs map 41 | environment := getenvironment(os.Environ(), func(item string) (key, val string) { 42 | splits := strings.Split(item, "=") 43 | key = splits[0] 44 | val = splits[1] 45 | return 46 | }) 47 | 48 | jsonEnv, err := json.Marshal(environment) 49 | 50 | util.Check(err) 51 | // convert to json string object 52 | return ctx.String(string(jsonEnv)) 53 | } 54 | 55 | return ctx.Null() 56 | } 57 | -------------------------------------------------------------------------------- /core/ops/fetch.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "github.com/elsaland/elsa/util" 5 | 6 | "github.com/elsaland/quickjs" 7 | "github.com/imroc/req" 8 | ) 9 | 10 | func Fetch(ctx *quickjs.Context, url quickjs.Value) quickjs.Value { 11 | r, err := req.Get(url.String()) 12 | util.Check(err) 13 | resp, err := r.ToString() 14 | util.Check(err) 15 | return ctx.String(resp) 16 | } 17 | -------------------------------------------------------------------------------- /core/ops/fs.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/elsaland/elsa/core/options" 11 | "github.com/elsaland/elsa/util" 12 | 13 | "github.com/elsaland/quickjs" 14 | "github.com/spf13/afero" 15 | ) 16 | 17 | type FsDriver struct { 18 | Perms *options.Perms 19 | Fs afero.Fs 20 | } 21 | 22 | var _ io.Reader = (*os.File)(nil) 23 | 24 | func (fs *FsDriver) ReadFile(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 25 | data, err := afero.ReadFile(fs.Fs, path.String()) 26 | util.Check(err) 27 | return ctx.String(string(data)) 28 | } 29 | 30 | func (fs *FsDriver) WriteFile(ctx *quickjs.Context, path quickjs.Value, content quickjs.Value) quickjs.Value { 31 | err := afero.WriteFile(fs.Fs, path.String(), []byte(content.String()), 0777) 32 | util.Check(err) 33 | return ctx.Bool(true) 34 | } 35 | 36 | func (fs *FsDriver) Exists(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 37 | data, err := afero.Exists(fs.Fs, path.String()) 38 | util.Check(err) 39 | return ctx.Bool(data) 40 | } 41 | 42 | func (fs *FsDriver) DirExists(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 43 | data, err := afero.DirExists(fs.Fs, path.String()) 44 | util.Check(err) 45 | return ctx.Bool(data) 46 | } 47 | 48 | func (fs *FsDriver) Cwd(ctx *quickjs.Context) quickjs.Value { 49 | dir, err := os.Getwd() 50 | util.Check(err) 51 | return ctx.String(dir) 52 | } 53 | 54 | type FileInfo struct { 55 | Name string 56 | Size int64 57 | Mode os.FileMode 58 | ModTime time.Time 59 | IsDir bool 60 | } 61 | 62 | func (fs *FsDriver) Stat(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 63 | entry, err := fs.Fs.Stat(path.String()) 64 | util.Check(err) 65 | f := FileInfo{ 66 | Name: entry.Name(), 67 | Size: entry.Size(), 68 | Mode: entry.Mode(), 69 | ModTime: entry.ModTime(), 70 | IsDir: entry.IsDir(), 71 | } 72 | output, err := json.Marshal(f) 73 | util.Check(err) 74 | return ctx.String(string(output)) 75 | } 76 | 77 | func (fs *FsDriver) Remove(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 78 | err := fs.Fs.Remove(path.String()) 79 | util.Check(err) 80 | return ctx.Bool(true) 81 | } 82 | 83 | func (fs *FsDriver) Mkdir(ctx *quickjs.Context, path quickjs.Value) quickjs.Value { 84 | err := fs.Fs.Mkdir(path.String(), os.FileMode(0777)) 85 | util.Check(err) 86 | return ctx.Bool(true) 87 | } 88 | 89 | type walkFs struct { 90 | Name string 91 | Size int64 92 | Mode os.FileMode 93 | ModTime time.Time 94 | IsDir bool 95 | Path string 96 | } 97 | 98 | func (fs *FsDriver) Walk(ctx *quickjs.Context, pathDir quickjs.Value) quickjs.Value { 99 | 100 | var files []walkFs 101 | 102 | err := filepath.Walk(pathDir.String(), func(path string, info os.FileInfo, err error) error { 103 | 104 | data := walkFs{ 105 | Name: info.Name(), 106 | Size: info.Size(), 107 | Mode: info.Mode(), 108 | ModTime: info.ModTime(), 109 | IsDir: info.IsDir(), 110 | Path: path, 111 | } 112 | 113 | if err != nil { 114 | util.Check(err) 115 | } 116 | 117 | files = append(files, data) 118 | 119 | return nil 120 | }) 121 | 122 | if err != nil { 123 | util.Check(err) 124 | } 125 | 126 | output, err := json.Marshal(files) 127 | util.Check(err) 128 | return ctx.String(string(output)) 129 | } 130 | -------------------------------------------------------------------------------- /core/ops/serve.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net" 7 | "net/url" 8 | 9 | "github.com/elsaland/quickjs" 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | // Serve listens for HTTP requests to host and calls callback sequentially on 14 | // every request. 15 | func Serve(ctx *quickjs.Context, callback func(request quickjs.Value) (response string), 16 | id quickjs.Value, host quickjs.Value) { 17 | var ( 18 | reqs = make(chan *fasthttp.RequestCtx) 19 | resps = make(chan Response) 20 | errch = make(chan error, 1) 21 | ) 22 | go func() { 23 | httpHandler := func(ctx *fasthttp.RequestCtx) { 24 | reqs <- ctx 25 | resp := <-resps 26 | ctx.SetStatusCode(int(resp.Status)) 27 | _, err := ctx.Write([]byte(resp.Body)) 28 | if err != nil { 29 | log.Fatalf("%v", err) 30 | } 31 | } 32 | errch <- fasthttp.ListenAndServe(host.String(), httpHandler) 33 | close(reqs) 34 | }() 35 | for { 36 | select { 37 | case <-errch: 38 | // TODO: throw the error as an exception to the JS script 39 | // see https://github.com/elsaland/elsa/issues/75 40 | break 41 | case req := <-reqs: 42 | reqjson, _ := json.Marshal(Request{ 43 | Method: string(req.Method()), 44 | URL: req.URI(), 45 | Header: req.Request.Header, 46 | Path: string(req.Path()), 47 | Host: string(req.Host()), 48 | QueryArgs: req.QueryArgs(), 49 | PostArgs: req.PostArgs(), 50 | PostForm: req.PostArgs(), 51 | RemoteAddr: req.RemoteAddr(), 52 | RequestURI: string(req.RequestURI()), 53 | Referer: string(req.Referer()), 54 | UserAgent: string(req.UserAgent()), 55 | LocalAddr: req.LocalAddr(), 56 | RemoteIP: req.RemoteIP(), 57 | LocalIP: req.LocalIP(), 58 | }) 59 | respjson := callback(ctx.String(string(reqjson))) 60 | var resp Response 61 | err := json.Unmarshal([]byte(respjson), &resp) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | resps <- resp 66 | } 67 | } 68 | } 69 | 70 | // Response response returned by callback from js 71 | type Response struct { 72 | // Status code 73 | Status int32 74 | // Body of the response 75 | Body string 76 | } 77 | 78 | type Request struct { 79 | Method string 80 | URL *fasthttp.URI 81 | Path string 82 | Header fasthttp.RequestHeader 83 | ContentLength int64 84 | TransferEncoding []string 85 | Host string 86 | Form url.Values 87 | PostForm *fasthttp.Args 88 | RemoteAddr net.Addr 89 | RequestURI string 90 | Referer string 91 | UserAgent string 92 | LocalAddr net.Addr 93 | RemoteIP net.IP 94 | LocalIP net.IP 95 | PostArgs *fasthttp.Args 96 | QueryArgs *fasthttp.Args 97 | } 98 | -------------------------------------------------------------------------------- /core/options/options.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "github.com/elsaland/quickjs" 5 | ) 6 | 7 | // Recv callback for an async op 8 | type Recv func(id quickjs.Value, val quickjs.Value) quickjs.Value 9 | 10 | // Elsa represents general data for the runtime 11 | type Elsa struct { 12 | // Permissions 13 | Perms *Perms 14 | // Async recv function 15 | Recv Recv 16 | } 17 | 18 | // Environment configure the runtime environment 19 | type Environment struct { 20 | // Enable or disable color logging 21 | NoColor bool 22 | // Command-line args to pass into Elsa.args 23 | Args []string 24 | // Whether to run tests associated with `Elsa.tests()` 25 | RunTests bool 26 | } 27 | 28 | // Perms permissions available for Elsa 29 | type Perms struct { 30 | // File system access 31 | Fs bool 32 | // Net access 33 | Net bool 34 | // Env access 35 | Env bool 36 | } 37 | 38 | // Options options for dispatching a new Elsa + QuickJS runtime 39 | type Options struct { 40 | // File name of the source (used for debuging purposes) 41 | SourceFile string 42 | // Source code 43 | Source string 44 | // Permission 45 | Perms *Perms 46 | // Configure Environment 47 | Env Environment 48 | } 49 | -------------------------------------------------------------------------------- /core/plugins.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package core 5 | 6 | import "plugin" 7 | 8 | // OpenPlugin open a dynamic lib and call the exported ElsaPlugin function 9 | // with the args provided by the plugin op 10 | // Currently, not compatible with windows 11 | func OpenPlugin(path string, arg interface{}) interface{} { 12 | // open the plugin 13 | p, err := plugin.Open(path) 14 | if err != nil { 15 | panic(err) 16 | } 17 | // lookup for exported variable and assign the argument 18 | v, err := p.Lookup("V") 19 | if err != nil { 20 | panic(err) 21 | } 22 | *v.(*interface{}) = arg 23 | // lookup for ElsaPlugin export and call the function 24 | f, err := p.Lookup("ElsaPlugin") 25 | if err != nil { 26 | panic(err) 27 | } 28 | return f.(func() interface{})() 29 | } 30 | -------------------------------------------------------------------------------- /core/plugins_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package core 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/elsaland/elsa/util" 10 | ) 11 | 12 | // OpenPlugin Go plugins have not yet been implemented on windows 13 | func OpenPlugin(path string, arg interface{}) interface{} { 14 | util.LogError("Not supported", "Go plugins are not supported for windows. See https://github.com/golang/go/issues/19282") 15 | os.Exit(1) 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /core/repl.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/elsaland/quickjs" 10 | ) 11 | 12 | // Repl implementation 13 | func Repl() { 14 | stringToEval := "" 15 | fmt.Println("Elsa REPL") 16 | fmt.Println("exit using ctrl+c or close()") 17 | 18 | for true { 19 | fmt.Print("> ") 20 | reader := bufio.NewReader(os.Stdin) 21 | text, _ := reader.ReadString('\n') 22 | 23 | fmt.Println(Eval(text, &stringToEval)) 24 | stringToEval += ";undefined;" 25 | } 26 | } 27 | 28 | // Eval js from string 29 | func Eval(text string, buffer *string) string { 30 | // repl close function 31 | closeEval := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 32 | os.Exit(1) 33 | return ctx.Null() 34 | } 35 | 36 | evalRuntime := quickjs.NewRuntime() 37 | defer evalRuntime.Free() 38 | 39 | evalContext := evalRuntime.NewContext() 40 | defer evalContext.Free() 41 | 42 | //TODO(buttercubz) set globals functions 43 | globalsEval := evalContext.Globals() 44 | 45 | globalsEval.Set("close", evalContext.Function(closeEval)) 46 | 47 | result, err := evalContext.Eval(*buffer + text) 48 | saveBuffer := check(err) 49 | 50 | if saveBuffer { 51 | *buffer += fmt.Sprintf(";undefined; %s", text) 52 | } 53 | 54 | defer result.Free() 55 | 56 | return result.String() 57 | } 58 | 59 | // check errors without exit 60 | func check(err error) bool { 61 | if err != nil { 62 | var evalErr *quickjs.Error 63 | if errors.As(err, &evalErr) { 64 | fmt.Println(evalErr.Cause) 65 | fmt.Println(evalErr.Stack) 66 | return false 67 | } 68 | } 69 | return true 70 | } 71 | -------------------------------------------------------------------------------- /core/run.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/elsaland/elsa/core/options" 5 | "github.com/elsaland/elsa/util" 6 | "io" 7 | 8 | "github.com/elsaland/quickjs" 9 | ) 10 | 11 | // PrepareRuntimeContext prepare the runtime and context with Elsa's internal ops 12 | // injects `__send` and `__recv` global dispatch functions into runtime 13 | func PrepareRuntimeContext(cxt *quickjs.Context, jsruntime quickjs.Runtime, args []string, flags *options.Perms, mode string) { 14 | // Assign perms 15 | elsa := &options.Elsa{Perms: flags} 16 | 17 | globals := cxt.Globals() 18 | // Attach send & recv global ops 19 | globals.SetFunction("__send", ElsaSendNS(elsa)) 20 | globals.SetFunction("__recv", ElsaRecvNS(elsa)) 21 | 22 | // Prepare runtime context with namespace and client op code 23 | // The snapshot is generated at bootstrap process 24 | snap, _ := Asset("target/elsa.js") 25 | k, err := cxt.Eval(string(snap)) 26 | util.Check(err) 27 | defer k.Free() 28 | 29 | ns := globals.Get("Elsa") 30 | defer ns.Free() 31 | // Assign `Elsa.args` with the os args 32 | _Args := cxt.Array() 33 | for i, arg := range args { 34 | _Arg := cxt.String(arg) 35 | _Args.SetByUint32(uint32(i), _Arg) 36 | } 37 | ns.Set("args", _Args) 38 | // Assing `Elsa.mode` with current environment mode 39 | _Mode := cxt.String(mode) 40 | ns.Set("mode", _Mode) 41 | // Runtime check to execute async jobs 42 | for { 43 | _, err = jsruntime.ExecutePendingJob() 44 | if err == io.EOF { 45 | err = nil 46 | break 47 | } 48 | util.Check(err) 49 | } 50 | } 51 | 52 | // Run create and dispatch a QuickJS runtime binded with Elsa's OPs configurable using options 53 | func Run(opt options.Options) { 54 | // Create a new quickJS runtime 55 | jsruntime := quickjs.NewRuntime() 56 | defer jsruntime.Free() 57 | 58 | // Create a new runtime context 59 | cxt := jsruntime.NewContext() 60 | defer cxt.Free() 61 | 62 | // mode is not configurable directly and is to be determined based on RunTests 63 | // defaults to `run` 64 | mode := "run" 65 | if opt.Env.RunTests { 66 | mode = "test" 67 | } 68 | 69 | // Prepare runtime and context with Elsa namespace 70 | PrepareRuntimeContext(cxt, jsruntime, opt.Env.Args, opt.Perms, mode) 71 | 72 | // Evaluate the source 73 | result, err := cxt.EvalFile(opt.Source, opt.SourceFile) 74 | util.Check(err) 75 | defer result.Free() 76 | 77 | // Check for exceptions 78 | if result.IsException() { 79 | err = cxt.Exception() 80 | util.Check(err) 81 | } 82 | 83 | for { 84 | _, err = jsruntime.ExecutePendingJob() 85 | if err == io.EOF { 86 | err = nil 87 | break 88 | } 89 | util.Check(err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /dev/dev.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "github.com/elsaland/elsa/core" 5 | "github.com/elsaland/elsa/core/options" 6 | "github.com/elsaland/quickjs" 7 | ) 8 | 9 | // AllowAll allow all flags when in development mode 10 | var AllowAll = options.Perms{ 11 | // Allow file system access 12 | Fs: true, 13 | // Allow net access 14 | Net: true, 15 | } 16 | 17 | // TypeCheck run typechecking and report the diagnostics 18 | func TypeCheck(source string, sourceFile string, args []string) { 19 | // Callback function for reporting diagnostics to the user 20 | report := func(val quickjs.Value) { 21 | ReportDiagnostics(val) 22 | } 23 | // Trigger the compiler with the report callback and source 24 | // allow all perms and specify os args 25 | Compile(source, sourceFile, report, &AllowAll, args) 26 | } 27 | 28 | // RunDev invoke typechecking and execute 29 | func RunDev(og string, opt options.Options) { 30 | // Allow all perms when running in development mode 31 | opt.Perms = &AllowAll 32 | // Run typechecking 33 | TypeCheck(og, opt.SourceFile, opt.Env.Args) 34 | // Execute bundled script into a quickJS runtime 35 | core.Run(opt) 36 | } 37 | -------------------------------------------------------------------------------- /dev/report.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elsaland/quickjs" 7 | ) 8 | 9 | // ReportDiagnostics report TypeScript diagnostics 10 | func ReportDiagnostics(diagnostics quickjs.Value) { 11 | diag := diagnostics.String() 12 | if diag != "" { 13 | fmt.Println(diagnostics.String()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dev/typescript.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/elsaland/elsa/core/options" 8 | "github.com/elsaland/elsa/util" 9 | 10 | "github.com/elsaland/elsa/core" 11 | "github.com/elsaland/quickjs" 12 | ) 13 | 14 | func Compile(source string, sourceFile string, fn func(val quickjs.Value), flags *options.Perms, args []string) { 15 | data, err := core.Asset("typescript/typescript.js") 16 | if err != nil { 17 | panic("Asset was not found.") 18 | } 19 | 20 | runtime.LockOSThread() 21 | jsruntime := quickjs.NewRuntime() 22 | defer jsruntime.Free() 23 | 24 | context := jsruntime.NewContext() 25 | defer context.Free() 26 | 27 | core.PrepareRuntimeContext(context, jsruntime, args, flags, "dev") 28 | 29 | globals := context.Globals() 30 | report := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 31 | fn(args[0]) 32 | return ctx.Null() 33 | } 34 | d := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { 35 | asset, er := core.Asset(args[0].String()) 36 | if er != nil { 37 | panic("Asset was not found.") 38 | } 39 | return ctx.String(string(asset)) 40 | } 41 | globals.Set("Report", context.Function(report)) 42 | globals.Set("Asset", context.Function(d)) 43 | bundle := string(data) + jsCheck(source, sourceFile) 44 | result, err := context.Eval(bundle) 45 | util.Check(err) 46 | defer result.Free() 47 | } 48 | 49 | func jsCheck(source, sourceFile string) string { 50 | return fmt.Sprintf("typeCheck(`%s`, `%s`);", sourceFile, source) 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elsaland/elsa 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Netflix/go-env v0.0.0-20200908232752-3e802f601e28 7 | github.com/andybalholm/brotli v1.0.1 // indirect 8 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef 9 | github.com/elsaland/quickjs v0.0.0-20200926030524-a9835b20920b 10 | github.com/evanw/esbuild v0.7.3-0.20200919185132-ef34da4ee06e 11 | github.com/fatih/color v1.9.0 12 | github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd 13 | github.com/go-bindata/go-bindata v3.1.2+incompatible 14 | github.com/imroc/req v0.3.0 15 | github.com/klauspost/compress v1.11.1 // indirect 16 | github.com/lithdew/quickjs v0.0.0-20200714182134-aaa42285c9d2 17 | github.com/mitchellh/go-homedir v1.1.0 18 | github.com/pelletier/go-toml v1.2.0 19 | github.com/spf13/afero v1.4.0 20 | github.com/spf13/cobra v1.0.0 21 | github.com/stretchr/testify v1.6.1 22 | github.com/tdewolff/minify/v2 v2.9.5 23 | github.com/valyala/fasthttp v1.16.0 24 | golang.org/x/sys v0.2.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /js/00_core.js: -------------------------------------------------------------------------------- 1 | // Define op codes 2 | const __ops = { 3 | FSWrite: 1, 4 | FSRead: 2, 5 | FSExists: 3, 6 | FSDirExists: 4, 7 | FSCwd: 5, 8 | FSStat: 6, 9 | Serve: 25, 10 | FSRemove: 7, 11 | Fetch: 20, 12 | Log: 10, 13 | Plugin: 15, 14 | FSMkdir: 9, 15 | Env: 11, 16 | Walk: 14, 17 | }; 18 | 19 | ((window) => { 20 | let initialized = false; 21 | let promiseTable = {}; 22 | let promiseNextId = 1; 23 | function init() { 24 | if (initialized) return; 25 | initialized = true; 26 | globalThis.__recv(__recvAsync); 27 | } 28 | 29 | function __recvAsync(id, val) { 30 | if (!id) return; 31 | return promiseTable[id].resolve(val); 32 | } 33 | 34 | async function __sendAsync(op, cb, ...args) { 35 | init(); 36 | const id = promiseNextId++; 37 | if (typeof cb == "function") { 38 | promiseTable[id] = { resolve: cb }; 39 | globalThis.__send(op, ...[id, ...args]); 40 | } else { 41 | let resolve, reject; 42 | const promise = new Promise((resolve_, reject_) => { 43 | resolve = resolve_; 44 | reject = reject_; 45 | }); 46 | promise.resolve = resolve; 47 | promise.reject = reject; 48 | 49 | promiseTable[id] = promise; 50 | 51 | globalThis.__send(op, ...[id, ...args]); 52 | 53 | const res = await promise; 54 | if (res.ok) return res.ok; 55 | else if (res.err) return res.err; 56 | else throw new Error("Unknown error"); 57 | } 58 | } 59 | 60 | Object.assign(window, { 61 | __sendAsync, 62 | }); 63 | })(globalThis); 64 | -------------------------------------------------------------------------------- /js/01_namespace.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | globalThis.Elsa = { 3 | readFile: (arg) => { 4 | return globalThis.__send(__ops.FSRead, arg); 5 | }, 6 | writeFile: (file, content) => { 7 | return globalThis.__send(__ops.FSWrite, file, content); 8 | }, 9 | exists: (arg) => { 10 | return globalThis.__send(__ops.FSExists, arg); 11 | }, 12 | stat: (arg) => { 13 | return JSON.parse(globalThis.__send(__ops.FSStat, arg)); 14 | }, 15 | serve: async function (host, cb) { 16 | globalThis.__sendAsync( 17 | __ops.Serve, 18 | function (data) { 19 | return JSON.stringify(cb(JSON.parse(data))); 20 | }, 21 | host 22 | ); 23 | }, 24 | remove: (arg) => { 25 | return globalThis.__send(__ops.FSRemove, arg); 26 | }, 27 | cwd: () => { 28 | return globalThis.__send(__ops.FSCwd); 29 | }, 30 | runPlugin: (dylib, arg) => { 31 | return globalThis.__send(__ops.Plugin, dylib, arg); 32 | }, 33 | mkdir: (arg) => { 34 | return globalThis.__send(__ops.FSMkdir, arg); 35 | }, 36 | env: { 37 | get: (arg) => { 38 | return globalThis.__send(__ops.Env, arg); 39 | }, 40 | set: (env, val) => { 41 | return globalThis.__send(__ops.Env, env, val); 42 | }, 43 | toObject: () => { 44 | return JSON.parse(globalThis.__send(__ops.Env, true)); 45 | }, 46 | }, 47 | *walk(path) { 48 | const files = JSON.parse(globalThis.__send(__ops.Walk, path)); 49 | 50 | for (const file of files) { 51 | yield file; 52 | } 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /js/02_console.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | // DOM console bindings 3 | globalThis.console = { 4 | trace: (...args) => { 5 | val = args[0]; 6 | globalThis.__send( 7 | __ops.Log, 8 | typeof val, 9 | typeof val == "object" ? JSON.stringify(val) : val 10 | ); 11 | }, 12 | debug: (...args) => { 13 | val = args[0]; 14 | globalThis.__send( 15 | __ops.Log, 16 | typeof val, 17 | typeof val == "object" ? JSON.stringify(val) : val 18 | ); 19 | }, 20 | log: (...args) => { 21 | val = args[0]; 22 | globalThis.__send( 23 | __ops.Log, 24 | typeof val, 25 | typeof val == "object" ? JSON.stringify(val) : val 26 | ); 27 | }, 28 | info: (...args) => { 29 | val = args[0]; 30 | globalThis.__send( 31 | __ops.Log, 32 | typeof val, 33 | typeof val == "object" ? JSON.stringify(val) : val 34 | ); 35 | }, 36 | warn: (...args) => { 37 | val = args[0]; 38 | globalThis.__send( 39 | __ops.Log, 40 | typeof val, 41 | typeof val == "object" ? JSON.stringify(val) : val 42 | ); 43 | }, 44 | error: (...args) => { 45 | val = args[0]; 46 | globalThis.__send( 47 | __ops.Log, 48 | typeof val, 49 | typeof val == "object" ? JSON.stringify(val) : val 50 | ); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /js/03_fetch.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | globalThis.fetch = async function (url) { 3 | return globalThis.__sendAsync(__ops.Fetch, false, url); 4 | }; 5 | -------------------------------------------------------------------------------- /js/04_event.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | 3 | /** 4 | * Class for managing events. 5 | * Can be extended to provide event functionality in other classes. 6 | * 7 | * @class EventEmitter Manages event registering and emitting. 8 | */ 9 | function EventEmitter() {} 10 | 11 | /** 12 | * Finds the index of the listener for the event in its storage array. 13 | * 14 | * @param {Function[]} listeners Array of listeners to search through. 15 | * @param {Function} listener Method to look for. 16 | * @return {Number} Index of the specified listener, -1 if not found 17 | * @api private 18 | */ 19 | function indexOfListener(listeners, listener) { 20 | var i = listeners.length; 21 | while (i--) { 22 | if (listeners[i].listener === listener) { 23 | return i; 24 | } 25 | } 26 | 27 | return -1; 28 | } 29 | 30 | /** 31 | * Alias a method while keeping the context correct, to allow for overwriting of target method. 32 | * 33 | * @param {String} name The name of the target method. 34 | * @return {Function} The aliased method 35 | * @api private 36 | */ 37 | function alias(name) { 38 | return function aliasClosure() { 39 | return this[name].apply(this, arguments); 40 | }; 41 | } 42 | 43 | /** 44 | * Returns the listener array for the specified event. 45 | * Will initialise the event object and listener arrays if required. 46 | * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. 47 | * Each property in the object response is an array of listener functions. 48 | * 49 | * @param {String|RegExp} evt Name of the event to return the listeners from. 50 | * @return {Function[]|Object} All listener functions for the event. 51 | */ 52 | EventEmitter.prototype.getListeners = function getListeners(evt) { 53 | var events = this._getEvents(); 54 | var response; 55 | var key; 56 | 57 | // Return a concatenated array of all matching events if 58 | // the selector is a regular expression. 59 | if (evt instanceof RegExp) { 60 | response = {}; 61 | for (key in events) { 62 | if (Object.hasOwnProperty(events, key) && evt.test(key)) { 63 | response[key] = events[key]; 64 | } 65 | } 66 | } else { 67 | response = events[evt] || (events[evt] = []); 68 | } 69 | 70 | return response; 71 | }; 72 | 73 | /** 74 | * Takes a list of listener objects and flattens it into a list of listener functions. 75 | * 76 | * @param {Object[]} listeners Raw listener objects. 77 | * @return {Function[]} Just the listener functions. 78 | */ 79 | EventEmitter.prototype.flattenListeners = function flattenListeners(listeners) { 80 | var flatListeners = []; 81 | var i; 82 | 83 | for (i = 0; i < listeners.length; i += 1) { 84 | flatListeners.push(listeners[i].listener); 85 | } 86 | 87 | return flatListeners; 88 | }; 89 | 90 | /** 91 | * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful. 92 | * 93 | * @param {String|RegExp} evt Name of the event to return the listeners from. 94 | * @return {Object} All listener functions for an event in an object. 95 | */ 96 | EventEmitter.prototype.getListenersAsObject = function getListenersAsObject( 97 | evt 98 | ) { 99 | var listeners = this.getListeners(evt); 100 | var response; 101 | 102 | if (listeners instanceof Array) { 103 | response = {}; 104 | response[evt] = listeners; 105 | } 106 | 107 | return response || listeners; 108 | }; 109 | 110 | function isValidListener(listener) { 111 | if (typeof listener === "function" || listener instanceof RegExp) { 112 | return true; 113 | } else if (listener && typeof listener === "object") { 114 | return isValidListener(listener.listener); 115 | } else { 116 | return false; 117 | } 118 | } 119 | 120 | /** 121 | * Adds a listener function to the specified event. 122 | * The listener will not be added if it is a duplicate. 123 | * If the listener returns true then it will be removed after it is called. 124 | * If you pass a regular expression as the event name then the listener will be added to all events that match it. 125 | * 126 | * @param {String|RegExp} evt Name of the event to attach the listener to. 127 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 128 | * @return {Object} Current instance of EventEmitter for chaining. 129 | */ 130 | EventEmitter.prototype.addListener = function addListener(evt, listener) { 131 | if (!isValidListener(listener)) { 132 | throw new TypeError("listener must be a function"); 133 | } 134 | 135 | var listeners = this.getListenersAsObject(evt); 136 | var listenerIsWrapped = typeof listener === "object"; 137 | var key; 138 | 139 | for (key in listeners) { 140 | if ( 141 | Object.hasOwnProperty(listeners, key) && 142 | indexOfListener(listeners[key], listener) === -1 143 | ) { 144 | listeners[key].push( 145 | listenerIsWrapped 146 | ? listener 147 | : { 148 | listener: listener, 149 | once: false, 150 | } 151 | ); 152 | } 153 | } 154 | 155 | return this; 156 | }; 157 | 158 | /** 159 | * Alias of addListener 160 | */ 161 | EventEmitter.prototype.on = alias("addListener"); 162 | 163 | /** 164 | * Semi-alias of addListener. It will add a listener that will be 165 | * automatically removed after its first execution. 166 | * 167 | * @param {String|RegExp} evt Name of the event to attach the listener to. 168 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 169 | * @return {Object} Current instance of EventEmitter for chaining. 170 | */ 171 | EventEmitter.prototype.addOnceListener = function addOnceListener( 172 | evt, 173 | listener 174 | ) { 175 | return this.addListener(evt, { 176 | listener: listener, 177 | once: true, 178 | }); 179 | }; 180 | 181 | /** 182 | * Alias of addOnceListener. 183 | */ 184 | EventEmitter.prototype.once = alias("addOnceListener"); 185 | 186 | /** 187 | * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad. 188 | * You need to tell it what event names should be matched by a regex. 189 | * 190 | * @param {String} evt Name of the event to create. 191 | * @return {Object} Current instance of EventEmitter for chaining. 192 | */ 193 | EventEmitter.prototype.defineEvent = function defineEvent(evt) { 194 | this.getListeners(evt); 195 | return this; 196 | }; 197 | 198 | /** 199 | * Uses defineEvent to define multiple events. 200 | * 201 | * @param {String[]} evts An array of event names to define. 202 | * @return {Object} Current instance of EventEmitter for chaining. 203 | */ 204 | EventEmitter.prototype.defineEvents = function defineEvents(evts) { 205 | for (var i = 0; i < evts.length; i += 1) { 206 | this.defineEvent(evts[i]); 207 | } 208 | return this; 209 | }; 210 | 211 | /** 212 | * Removes a listener function from the specified event. 213 | * When passed a regular expression as the event name, it will remove the listener from all events that match it. 214 | * 215 | * @param {String|RegExp} evt Name of the event to remove the listener from. 216 | * @param {Function} listener Method to remove from the event. 217 | * @return {Object} Current instance of EventEmitter for chaining. 218 | */ 219 | EventEmitter.prototype.removeListener = function removeListener(evt, listener) { 220 | var listeners = this.getListenersAsObject(evt); 221 | var index; 222 | var key; 223 | 224 | for (key in listeners) { 225 | if (Object.hasOwnProperty(listeners, key)) { 226 | index = indexOfListener(listeners[key], listener); 227 | 228 | if (index !== -1) { 229 | listeners[key].splice(index, 1); 230 | } 231 | } 232 | } 233 | 234 | return this; 235 | }; 236 | 237 | /** 238 | * Alias of removeListener 239 | */ 240 | EventEmitter.prototype.off = alias("removeListener"); 241 | 242 | /** 243 | * Adds listeners in bulk using the manipulateListeners method. 244 | * If you pass an object as the first argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added. 245 | * You can also pass it a regular expression to add the array of listeners to all events that match it. 246 | * Yeah, this function does quite a bit. That's probably a bad thing. 247 | * 248 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. 249 | * @param {Function[]} [listeners] An optional array of listener functions to add. 250 | * @return {Object} Current instance of EventEmitter for chaining. 251 | */ 252 | EventEmitter.prototype.addListeners = function addListeners(evt, listeners) { 253 | // Pass through to manipulateListeners 254 | return this.manipulateListeners(false, evt, listeners); 255 | }; 256 | 257 | /** 258 | * Removes listeners in bulk using the manipulateListeners method. 259 | * If you pass an object as the first argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 260 | * You can also pass it an event name and an array of listeners to be removed. 261 | * You can also pass it a regular expression to remove the listeners from all events that match it. 262 | * 263 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. 264 | * @param {Function[]} [listeners] An optional array of listener functions to remove. 265 | * @return {Object} Current instance of EventEmitter for chaining. 266 | */ 267 | EventEmitter.prototype.removeListeners = function removeListeners( 268 | evt, 269 | listeners 270 | ) { 271 | // Pass through to manipulateListeners 272 | return this.manipulateListeners(true, evt, listeners); 273 | }; 274 | 275 | /** 276 | * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. 277 | * The first argument will determine if the listeners are removed (true) or added (false). 278 | * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 279 | * You can also pass it an event name and an array of listeners to be added/removed. 280 | * You can also pass it a regular expression to manipulate the listeners of all events that match it. 281 | * 282 | * @param {Boolean} remove True if you want to remove listeners, false if you want to add. 283 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. 284 | * @param {Function[]} [listeners] An optional array of listener functions to add/remove. 285 | * @return {Object} Current instance of EventEmitter for chaining. 286 | */ 287 | EventEmitter.prototype.manipulateListeners = function manipulateListeners( 288 | remove, 289 | evt, 290 | listeners 291 | ) { 292 | var i; 293 | var value; 294 | var single = remove ? this.removeListener : this.addListener; 295 | var multiple = remove ? this.removeListeners : this.addListeners; 296 | 297 | // If evt is an object then pass each of its properties to this method 298 | if (typeof evt === "object" && !(evt instanceof RegExp)) { 299 | for (i in evt) { 300 | if (Object.hasOwnProperty(evt, i) && (value = evt[i])) { 301 | // Pass the single listener straight through to the singular method 302 | if (typeof value === "function") { 303 | single.call(this, i, value); 304 | } else { 305 | // Otherwise pass back to the multiple function 306 | multiple.call(this, i, value); 307 | } 308 | } 309 | } 310 | } else { 311 | // So evt must be a string 312 | // And listeners must be an array of listeners 313 | // Loop over it and pass each one to the multiple method 314 | i = listeners.length; 315 | while (i--) { 316 | single.call(this, evt, listeners[i]); 317 | } 318 | } 319 | 320 | return this; 321 | }; 322 | 323 | /** 324 | * Removes all listeners from a specified event. 325 | * If you do not specify an event then all listeners will be removed. 326 | * That means every event will be emptied. 327 | * You can also pass a regex to remove all events that match it. 328 | * 329 | * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. 330 | * @return {Object} Current instance of EventEmitter for chaining. 331 | */ 332 | EventEmitter.prototype.removeEvent = function removeEvent(evt) { 333 | var type = typeof evt; 334 | var events = this._getEvents(); 335 | var key; 336 | 337 | // Remove different things depending on the state of evt 338 | if (type === "string") { 339 | // Remove all listeners for the specified event 340 | delete events[evt]; 341 | } else if (evt instanceof RegExp) { 342 | // Remove all events matching the regex. 343 | for (key in events) { 344 | if (Object.hasOwnProperty(events, key) && evt.test(key)) { 345 | delete events[key]; 346 | } 347 | } 348 | } else { 349 | // Remove all listeners in all events 350 | delete this._events; 351 | } 352 | 353 | return this; 354 | }; 355 | 356 | /** 357 | * Alias of removeEvent. 358 | * 359 | * Added to mirror the node API. 360 | */ 361 | EventEmitter.prototype.removeAllListeners = alias("removeEvent"); 362 | 363 | /** 364 | * Emits an event of your choice. 365 | * When emitted, every listener attached to that event will be executed. 366 | * If you pass the optional argument array then those arguments will be passed to every listener upon execution. 367 | * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. 368 | * So they will not arrive within the array on the other side, they will be separate. 369 | * You can also pass a regular expression to emit to all events that match it. 370 | * 371 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 372 | * @param {Array} [args] Optional array of arguments to be passed to each listener. 373 | * @return {Object} Current instance of EventEmitter for chaining. 374 | */ 375 | EventEmitter.prototype.emitEvent = function emitEvent(evt, args) { 376 | var listenersMap = this.getListenersAsObject(evt); 377 | var listeners; 378 | var listener; 379 | var i; 380 | var key; 381 | var response; 382 | 383 | for (key in listenersMap) { 384 | if (Object.hasOwnProperty(listenersMap, key)) { 385 | listeners = listenersMap[key].slice(0); 386 | 387 | for (i = 0; i < listeners.length; i++) { 388 | // If the listener returns true then it shall be removed from the event 389 | // The function is executed either with a basic call or an apply if there is an args array 390 | listener = listeners[i]; 391 | 392 | if (listener.once === true) { 393 | this.removeListener(evt, listener.listener); 394 | } 395 | 396 | response = listener.listener.apply(this, args || []); 397 | 398 | if (response === this._getOnceReturnValue()) { 399 | this.removeListener(evt, listener.listener); 400 | } 401 | } 402 | } 403 | } 404 | 405 | return this; 406 | }; 407 | 408 | /** 409 | * Alias of emitEvent 410 | */ 411 | EventEmitter.prototype.trigger = alias("emitEvent"); 412 | 413 | /** 414 | * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on. 415 | * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it. 416 | * 417 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 418 | * @param {...*} Optional additional arguments to be passed to each listener. 419 | * @return {Object} Current instance of EventEmitter for chaining. 420 | */ 421 | EventEmitter.prototype.emit = function emit(evt) { 422 | var args = Array.prototype.slice.call(arguments, 1); 423 | return this.emitEvent(evt, args); 424 | }; 425 | 426 | /** 427 | * Sets the current value to check against when executing listeners. If a 428 | * listeners return value matches the one set here then it will be removed 429 | * after execution. This value defaults to true. 430 | * 431 | * @param {*} value The new value to check for when executing listeners. 432 | * @return {Object} Current instance of EventEmitter for chaining. 433 | */ 434 | EventEmitter.prototype.setOnceReturnValue = function setOnceReturnValue(value) { 435 | this._onceReturnValue = value; 436 | return this; 437 | }; 438 | 439 | /** 440 | * Fetches the current value to check against when executing listeners. If 441 | * the listeners return value matches this one then it should be removed 442 | * automatically. It will return true by default. 443 | * 444 | * @return {*|Boolean} The current value to check for or the default, true. 445 | * @api private 446 | */ 447 | EventEmitter.prototype._getOnceReturnValue = function _getOnceReturnValue() { 448 | if (this.hasOwnProperty("_onceReturnValue")) { 449 | return this._onceReturnValue; 450 | } else { 451 | return true; 452 | } 453 | }; 454 | 455 | /** 456 | * Fetches the events object and creates one if required. 457 | * 458 | * @return {Object} The events storage object. 459 | * @api private 460 | */ 461 | EventEmitter.prototype._getEvents = function _getEvents() { 462 | return this._events || (this._events = {}); 463 | }; 464 | 465 | globalThis.EventEmitter = EventEmitter; 466 | -------------------------------------------------------------------------------- /js/05_compiler.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | const IGNORED_DIAGNOSTICS = [ 3 | // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is 4 | // not a module. 5 | 2306, 6 | // TS1375: 'await' expressions are only allowed at the top level of a file 7 | // when that file is a module, but this file has no imports or exports. 8 | // Consider adding an empty 'export {}' to make this file a module. 9 | 1375, 10 | // TS1103: 'for-await-of' statement is only allowed within an async function 11 | // or async generator. 12 | 1103, 13 | // TS2691: An import path cannot end with a '.ts' extension. Consider 14 | // importing 'bad-module' instead. 15 | 2691, 16 | // TS5009: Cannot find the common subdirectory path for the input files. 17 | 5009, 18 | // TS5055: Cannot write file 19 | // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js' 20 | // because it would overwrite input file. 21 | 5055, 22 | // TypeScript is overly opinionated that only CommonJS modules kinds can 23 | // support JSON imports. Allegedly this was fixed in 24 | // Microsoft/TypeScript#26825 but that doesn't seem to be working here, 25 | // so we will ignore complaints about this compiler setting. 26 | 5070, 27 | // TS7016: Could not find a declaration file for module '...'. '...' 28 | // implicitly has an 'any' type. This is due to `allowJs` being off by 29 | // default but importing of a JavaScript module. 30 | 7016, 31 | ]; 32 | 33 | const options = { allowNonTsExtensions: true }; 34 | 35 | function typeCheck(file, source) { 36 | const dummyFilePath = file; 37 | const textAst = ts.createSourceFile( 38 | dummyFilePath, 39 | source, 40 | ts.ScriptTarget.ES6 41 | ); 42 | const dtsAST = ts.createSourceFile( 43 | "/lib.es6.d.ts", 44 | Asset("typescript/lib.es6.d.ts"), 45 | ts.ScriptTarget.ES6 46 | ); 47 | 48 | const files = { [dummyFilePath]: textAst, "/lib.es6.d.ts": dtsAST }; 49 | const host = { 50 | fileExists: (filePath) => { 51 | return files[filePath] != null || Elsa.exists(filePath); 52 | }, 53 | directoryExists: (dirPath) => dirPath === "/", 54 | getCurrentDirectory: () => Elsa.cwd(), 55 | getDirectories: () => [], 56 | getCanonicalFileName: (fileName) => fileName, 57 | getNewLine: () => "\n", 58 | getDefaultLibFileName: () => "/lib.es6.d.ts", 59 | getSourceFile: (filePath) => { 60 | if (files[filePath] != null) return files[filePath]; 61 | else { 62 | return ts.createSourceFile( 63 | filePath, 64 | Elsa.readFile(filePath), 65 | ts.ScriptTarget.ES6 66 | ); 67 | } 68 | }, 69 | readFile: (filePath) => { 70 | return filePath === dummyFilePath ? text : Elsa.readFile(filePath); 71 | }, 72 | useCaseSensitiveFileNames: () => true, 73 | writeFile: () => {}, 74 | resolveModuleNames, 75 | }; 76 | const program = ts.createProgram({ 77 | options, 78 | rootNames: [dummyFilePath], 79 | host, 80 | }); 81 | 82 | let diag = ts.getPreEmitDiagnostics(program).filter(function ({ code }) { 83 | return code != 5023 && !IGNORED_DIAGNOSTICS.includes(code); 84 | }); 85 | let diags = ts.formatDiagnosticsWithColorAndContext(diag, host); 86 | Report(diags); 87 | } 88 | 89 | function resolveModuleNames(moduleNames, containingFile) { 90 | const resolvedModules = []; 91 | for (const moduleName of moduleNames) { 92 | let fileName = join(containingFile, "..", moduleName); 93 | if (moduleName.startsWith("https://")) { 94 | fileName = moduleName.replace("https://", "/tmp/"); 95 | } 96 | resolvedModules.push({ resolvedFileName: fileName }); 97 | } 98 | return resolvedModules; 99 | } 100 | 101 | // Joins path segments. Preserves initial "/" and resolves ".." and "." 102 | // Does not support using ".." to go above/outside the root. 103 | // This means that join("foo", "../../bar") will not resolve to "../bar" 104 | function join(/* path segments */) { 105 | // Split the inputs into a list of path commands. 106 | var parts = []; 107 | for (var i = 0, l = arguments.length; i < l; i++) { 108 | parts = parts.concat(arguments[i].split("/")); 109 | } 110 | // Interpret the path commands to get the new resolved path. 111 | var newParts = []; 112 | for (i = 0, l = parts.length; i < l; i++) { 113 | var part = parts[i]; 114 | // Remove leading and trailing slashes 115 | // Also remove "." segments 116 | if (!part || part === ".") continue; 117 | // Interpret ".." to pop the last segment 118 | if (part === "..") newParts.pop(); 119 | // Push new path segments. 120 | else newParts.push(part); 121 | } 122 | // Preserve the initial slash if there was one. 123 | if (parts[0] === "") newParts.unshift(""); 124 | // Turn back into a single string path. 125 | return newParts.join("/") || (newParts.length ? "/" : "."); 126 | } 127 | -------------------------------------------------------------------------------- /js/06_encoder.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | var utf8Encodings = ["utf8", "utf-8", "unicode-1-1-utf-8"]; 3 | 4 | function TextEncoder(encoding) { 5 | if ( 6 | utf8Encodings.indexOf(encoding) < 0 && 7 | typeof encoding !== "undefined" && 8 | encoding != null 9 | ) { 10 | throw new RangeError("Invalid encoding type. Only utf-8 is supported"); 11 | } else { 12 | this.encoding = "utf-8"; 13 | this.encode = function (str) { 14 | if (typeof str !== "string") { 15 | throw new TypeError("passed argument must be of tye string"); 16 | } 17 | var binstr = unescape(encodeURIComponent(str)), 18 | arr = new Uint8Array(binstr.length); 19 | const split = binstr.split(""); 20 | for (let i = 0; i < split.length; i++) { 21 | arr[i] = split[i].charCodeAt(0); 22 | } 23 | return arr; 24 | }; 25 | } 26 | } 27 | 28 | function TextDecoder(encoding) { 29 | if ( 30 | utf8Encodings.indexOf(encoding) < 0 && 31 | typeof encoding !== "undefined" && 32 | encoding != null 33 | ) { 34 | throw new RangeError("Invalid encoding type. Only utf-8 is supported"); 35 | } else { 36 | this.encoding = "utf-8"; 37 | this.decode = function (view, options) { 38 | if (typeof view === "undefined") { 39 | return ""; 40 | } 41 | 42 | var stream = 43 | typeof options !== "undefined" && stream in options 44 | ? options.stream 45 | : false; 46 | if (typeof stream !== "boolean") { 47 | throw new TypeError("stream option must be boolean"); 48 | } 49 | 50 | if (!ArrayBuffer.isView(view)) { 51 | throw new TypeError("passed argument must be an array buffer view"); 52 | } else { 53 | var arr = new Uint8Array(view.buffer, view.byteOffset, view.byteLength), 54 | charArr = new Array(arr.length); 55 | for (let i = 0; i < arr.length; i++) { 56 | charArr[i] = String.fromCharCode(arr[i]); 57 | } 58 | return decodeURIComponent(escape(charArr.join(""))); 59 | } 60 | }; 61 | } 62 | } 63 | 64 | /** 65 | * Implementation of atob() according to the HTML and Infra specs, except that 66 | * instead of throwing INVALID_CHARACTER_ERR we return null. 67 | */ 68 | function atob(data) { 69 | // Web IDL requires DOMStrings to just be converted using ECMAScript 70 | // ToString, which in our case amounts to using a template literal. 71 | data = `${data}`; 72 | // "Remove all ASCII whitespace from data." 73 | data = data.replace(/[ \t\n\f\r]/g, ""); 74 | // "If data's length divides by 4 leaving no remainder, then: if data ends 75 | // with one or two U+003D (=) code points, then remove them from data." 76 | if (data.length % 4 === 0) { 77 | data = data.replace(/==?$/, ""); 78 | } 79 | // "If data's length divides by 4 leaving a remainder of 1, then return 80 | // failure." 81 | // 82 | // "If data contains a code point that is not one of 83 | // 84 | // U+002B (+) 85 | // U+002F (/) 86 | // ASCII alphanumeric 87 | // 88 | // then return failure." 89 | if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) { 90 | return null; 91 | } 92 | // "Let output be an empty byte sequence." 93 | let output = ""; 94 | // "Let buffer be an empty buffer that can have bits appended to it." 95 | // 96 | // We append bits via left-shift and or. accumulatedBits is used to track 97 | // when we've gotten to 24 bits. 98 | let buffer = 0; 99 | let accumulatedBits = 0; 100 | // "Let position be a position variable for data, initially pointing at the 101 | // start of data." 102 | // 103 | // "While position does not point past the end of data:" 104 | for (let i = 0; i < data.length; i++) { 105 | // "Find the code point pointed to by position in the second column of 106 | // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in 107 | // the first cell of the same row. 108 | // 109 | // "Append to buffer the six bits corresponding to n, most significant bit 110 | // first." 111 | // 112 | // atobLookup() implements the table from RFC 4648. 113 | buffer <<= 6; 114 | buffer |= atobLookup(data[i]); 115 | accumulatedBits += 6; 116 | // "If buffer has accumulated 24 bits, interpret them as three 8-bit 117 | // big-endian numbers. Append three bytes with values equal to those 118 | // numbers to output, in the same order, and then empty buffer." 119 | if (accumulatedBits === 24) { 120 | output += String.fromCharCode((buffer & 0xff0000) >> 16); 121 | output += String.fromCharCode((buffer & 0xff00) >> 8); 122 | output += String.fromCharCode(buffer & 0xff); 123 | buffer = accumulatedBits = 0; 124 | } 125 | // "Advance position by 1." 126 | } 127 | // "If buffer is not empty, it contains either 12 or 18 bits. If it contains 128 | // 12 bits, then discard the last four and interpret the remaining eight as 129 | // an 8-bit big-endian number. If it contains 18 bits, then discard the last 130 | // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append 131 | // the one or two bytes with values equal to those one or two numbers to 132 | // output, in the same order." 133 | if (accumulatedBits === 12) { 134 | buffer >>= 4; 135 | output += String.fromCharCode(buffer); 136 | } else if (accumulatedBits === 18) { 137 | buffer >>= 2; 138 | output += String.fromCharCode((buffer & 0xff00) >> 8); 139 | output += String.fromCharCode(buffer & 0xff); 140 | } 141 | // "Return output." 142 | return output; 143 | } 144 | /** 145 | * A lookup table for atob(), which converts an ASCII character to the 146 | * corresponding six-bit number. 147 | */ 148 | 149 | const keystr = 150 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 151 | 152 | function atobLookup(chr) { 153 | const index = keystr.indexOf(chr); 154 | // Throw exception if character is not in the lookup string; should not be hit in tests 155 | return index < 0 ? undefined : index; 156 | } 157 | 158 | /** 159 | * btoa() as defined by the HTML and Infra specs, which mostly just references 160 | * RFC 4648. 161 | */ 162 | function btoa(s) { 163 | let i; 164 | // String conversion as required by Web IDL. 165 | s = `${s}`; 166 | // "The btoa() method must throw an "InvalidCharacterError" DOMException if 167 | // data contains any character whose code point is greater than U+00FF." 168 | for (i = 0; i < s.length; i++) { 169 | if (s.charCodeAt(i) > 255) { 170 | return null; 171 | } 172 | } 173 | let out = ""; 174 | for (i = 0; i < s.length; i += 3) { 175 | const groupsOfSix = [undefined, undefined, undefined, undefined]; 176 | groupsOfSix[0] = s.charCodeAt(i) >> 2; 177 | groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4; 178 | if (s.length > i + 1) { 179 | groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4; 180 | groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2; 181 | } 182 | if (s.length > i + 2) { 183 | groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6; 184 | groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f; 185 | } 186 | for (let j = 0; j < groupsOfSix.length; j++) { 187 | if (typeof groupsOfSix[j] === "undefined") { 188 | out += "="; 189 | } else { 190 | out += btoaLookup(groupsOfSix[j]); 191 | } 192 | } 193 | } 194 | return out; 195 | } 196 | 197 | function btoaLookup(index) { 198 | if (index >= 0 && index < 64) { 199 | return keystr[index]; 200 | } 201 | 202 | // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. 203 | return undefined; 204 | } 205 | 206 | globalThis.TextEncoder = TextEncoder; 207 | globalThis.TextDecoder = TextDecoder; 208 | globalThis.atob = atob; 209 | globalThis.btoa = btoa; 210 | -------------------------------------------------------------------------------- /js/07_testing.js: -------------------------------------------------------------------------------- 1 | Elsa.tests = function (tests) { 2 | // Run tests only when mode is `test` 3 | // Will not run when running a script 4 | if (Elsa.mode !== "test") return; 5 | 6 | // Keep track for failures 7 | let failures = 0; 8 | // Loop through tests and run the function passed. 9 | // It basically `try...catch`'s the run and determines its failure. 10 | // No additional helper utilities are provided. 11 | let passed = 0; 12 | const startTestTime = Date.now(); 13 | 14 | const red = "\u001b[31m"; 15 | const green = "\u001b[32m"; 16 | const reset = "\u001b[0m"; 17 | const gray = "\u001b[38;5;8m"; 18 | const greenBG = "\u001b[42m"; 19 | const redBG = "\u001b[41m"; 20 | 21 | for (let testName in tests) { 22 | let testAction = tests[testName]; 23 | const timeBeforeStart = Date.now(); 24 | try { 25 | testAction(); 26 | 27 | console.log( 28 | `TEST ${testName} ... ${greenBG} OK ${reset} ${gray}(${ 29 | Date.now() - timeBeforeStart 30 | }ms)${reset}\n` 31 | ); 32 | 33 | passed++; 34 | } catch (e) { 35 | failures++; 36 | console.error( 37 | `TEST ${testName} ... ${redBG} FAILED ${reset} ${gray}(${ 38 | Date.now() - timeBeforeStart 39 | }ms)${reset} \n ${e}` 40 | ); 41 | console.error(e.stack); 42 | } 43 | } 44 | const endTestTime = Date.now() - startTestTime; 45 | 46 | console.log( 47 | `TEST results: ${passed} passed, ${failures} failed, ${ 48 | passed + failures 49 | } total, ${gray}(${endTestTime}ms)${reset}` 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /js/10_window.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license. 2 | // window.Elsa = globalThis.Elsa = Elsa 3 | globalThis.window = globalThis; 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/elsaland/elsa/dev" 5 | "runtime" 6 | 7 | "github.com/elsaland/elsa/bundler" 8 | "github.com/elsaland/elsa/cmd" 9 | "github.com/elsaland/elsa/core" 10 | ) 11 | 12 | func main() { 13 | runtime.LockOSThread() 14 | cmd.Execute(cmd.Elsa{ 15 | Run: core.Run, 16 | Bundle: bundler.BundleModule, 17 | Dev: dev.RunDev, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /mod.toml: -------------------------------------------------------------------------------- 1 | [module] 2 | name = "elsa" 3 | version = "0.0.0" 4 | authors = ["the elsaland team"] 5 | license = "MIT" 6 | 7 | [options] 8 | no_color = false 9 | -------------------------------------------------------------------------------- /module/config.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/Netflix/go-env" 5 | "github.com/pelletier/go-toml" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type module struct { 11 | Name string `toml:"name"` 12 | Desc string `toml:"description"` 13 | Version string `toml:"version"` 14 | License string `toml:"license"` 15 | Authors []string `toml:"authors"` 16 | } 17 | 18 | type options struct { 19 | NoColor bool `toml:"no_color" env:"NO_COLOR"` 20 | } 21 | 22 | type Config struct { 23 | Module module `toml:"module"` 24 | Options options `toml:"options"` 25 | } 26 | 27 | var DefaultConfigPath = "mod.toml" 28 | 29 | func ConfigLoad() (*Config, error) { 30 | cfg := Config{} 31 | 32 | if _, err := os.Stat(DefaultConfigPath); err == nil || os.IsExist(err) { 33 | buf, err := ioutil.ReadFile(DefaultConfigPath) 34 | if err != nil { 35 | return nil, err 36 | } 37 | err = toml.Unmarshal(buf, &cfg) 38 | if err != nil { 39 | return nil, err 40 | } 41 | } 42 | 43 | _, err := env.UnmarshalFromEnviron(&cfg) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &cfg, nil 49 | } 50 | 51 | func ConfigParse(source []byte) (*Config, error) { 52 | cfg := Config{} 53 | 54 | if source != nil { 55 | err := toml.Unmarshal(source, &cfg) 56 | if err != nil { 57 | return nil, err 58 | } 59 | } 60 | 61 | _, err := env.UnmarshalFromEnviron(&cfg) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return &cfg, nil 67 | } 68 | 69 | func ConfigExists() bool { 70 | if _, err := os.Stat(DefaultConfigPath); err == nil || os.IsExist(err) { 71 | return true 72 | } 73 | return false 74 | } 75 | -------------------------------------------------------------------------------- /packager/exec.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func ExecBuild(path string) { 11 | cmd := exec.Command("go", "build", ".") 12 | cmd.Dir = path 13 | var out bytes.Buffer 14 | var stderr bytes.Buffer 15 | cmd.Stdout = &out 16 | cmd.Stderr = &stderr 17 | err := cmd.Run() 18 | if err != nil { 19 | fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) 20 | os.Exit(1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packager/packager.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "github.com/elsaland/elsa/module" 5 | "github.com/elsaland/elsa/util" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/go-bindata/go-bindata" 10 | ) 11 | 12 | // PkgSource pack bundled js source into an executable 13 | func PkgSource(source string) { 14 | c := bindata.NewConfig() 15 | 16 | input := parseInput(source) 17 | if module.ConfigExists() { 18 | config := parseInput(module.DefaultConfigPath) 19 | c.Input = []bindata.InputConfig{input, config} 20 | } else { 21 | c.Input = []bindata.InputConfig{input} 22 | } 23 | c.Output = "target/elsa-package/asset.go" 24 | 25 | err := bindata.Translate(c) 26 | util.Check(err) 27 | 28 | entry := GeneratePkgSource(source) 29 | f, _ := os.Create("target/elsa-package/main.go") 30 | 31 | defer f.Close() 32 | _, err = f.WriteString(entry) 33 | util.Check(err) 34 | 35 | ExecBuild("target/elsa-package") 36 | } 37 | 38 | func parseInput(path string) bindata.InputConfig { 39 | return bindata.InputConfig{ 40 | Path: filepath.Clean(path), 41 | Recursive: false, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packager/template.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elsaland/elsa/module" 7 | ) 8 | 9 | const source string = ` 10 | package main 11 | 12 | import ( 13 | "os" 14 | "github.com/elsaland/elsa/core" 15 | "github.com/elsaland/elsa/module" 16 | "github.com/elsaland/elsa/core/options" 17 | ) 18 | 19 | func main() { 20 | snap, _ := Asset("%s") 21 | toml, _ := Asset("%s") 22 | config, _ := module.ConfigParse(toml) 23 | env := options.Environment{ 24 | NoColor: config.Options.NoColor, 25 | Args: os.Args[1:], 26 | } 27 | opt := options.Options{ 28 | SourceFile: "elsa.js", 29 | Source: string(snap), 30 | Perms: &options.Perms{Fs: true}, 31 | Env: env, 32 | } 33 | core.Run(opt) 34 | } 35 | ` 36 | 37 | func GeneratePkgSource(path string) string { 38 | return fmt.Sprintf(source, path, module.DefaultConfigPath) 39 | } 40 | -------------------------------------------------------------------------------- /std/README.md: -------------------------------------------------------------------------------- 1 | ## Standard modules 2 | 3 | Elsa's standard modules are largely inspired and ported from [Deno](https://deno.land/std) and Go's std 4 | 5 | 6 | -------------------------------------------------------------------------------- /std/async/README.md: -------------------------------------------------------------------------------- 1 | # `async` 2 | 3 | `async` is a module to provide help with aysncronous tasks. 4 | 5 | The async module has been _completely_ ported from Deno's standard modules. 6 | 7 | - Deferred - Creates a Promise with the `reject` and `resolve` functions. 8 | - Delay - Resolve a Promise after a given amount of milliseconds 9 | - MuxAsyncIterator - The MuxAsyncIterator class multiplexes multiple async iterators into a single 10 | stream. The class makes an assumption that the final result (the value returned and not 11 | yielded from the iterator) does not matter. If there is any result, it is 12 | discarded. 13 | - pooledMap - Transform values from an (async) iterable into another async iterable. The 14 | transforms are done concurrently, with a max concurrency defined by the 15 | poolLimit. 16 | -------------------------------------------------------------------------------- /std/async/deferred.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | // TODO(ry) It'd be better to make Deferred a class that inherits from 3 | // Promise, rather than an interface. This is possible in ES2016, however 4 | // TypeScript produces broken code when targeting ES5 code. 5 | // See https://github.com/Microsoft/TypeScript/issues/15202 6 | // At the time of writing, the github issue is closed but the problem remains. 7 | export interface Deferred extends Promise { 8 | resolve: (value?: T | PromiseLike) => void; 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | reject: (reason?: any) => void; 11 | } 12 | /** Creates a Promise with the `reject` and `resolve` functions 13 | * placed as methods on the promise object itself. It allows you to do: 14 | * 15 | * const p = deferred(); 16 | * // ... 17 | * p.resolve(42); 18 | */ 19 | export function deferred(): Deferred { 20 | let methods; 21 | const promise = new Promise((resolve, reject): void => { 22 | methods = { resolve, reject }; 23 | }); 24 | return Object.assign(promise, methods) as Deferred; 25 | } 26 | -------------------------------------------------------------------------------- /std/async/deferred_test.ts: -------------------------------------------------------------------------------- 1 | import { deferred } from "./deferred.ts"; 2 | 3 | Elsa.tests({ 4 | "[async] deferred: resolve": async function () { 5 | const d = deferred(); 6 | d.resolve("❄"); 7 | if ((await d) !== "❄") throw new Error("Assertion failed"); 8 | }, 9 | "[async] deferred: reject": async function (): Promise { 10 | const d = deferred(); 11 | d.reject(new Error("An elsa error ❄")); 12 | d.then(() => { 13 | throw new Error("should fail"); 14 | }); 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /std/async/delay.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /* Resolves after the given number of milliseconds. */ 3 | export function delay(ms: number): Promise { 4 | return new Promise((res): number => 5 | setTimeout((): void => { 6 | res(); 7 | }, ms) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /std/async/mux_async_iterator.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | import { Deferred, deferred } from "./deferred.ts"; 3 | 4 | interface TaggedYieldedValue { 5 | iterator: AsyncIterableIterator; 6 | value: T; 7 | } 8 | /** The MuxAsyncIterator class multiplexes multiple async iterators into a 9 | * single stream. It currently makes an assumption: 10 | * - The final result (the value returned and not yielded from the iterator) 11 | * does not matter; if there is any, it is discarded. 12 | */ 13 | export class MuxAsyncIterator implements AsyncIterable { 14 | private iteratorCount = 0; 15 | private yields: Array> = []; 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | private throws: any[] = []; 18 | private signal: Deferred = deferred(); 19 | 20 | add(iterator: AsyncIterableIterator): void { 21 | ++this.iteratorCount; 22 | this.callIteratorNext(iterator); 23 | } 24 | 25 | private async callIteratorNext( 26 | iterator: AsyncIterableIterator 27 | ): Promise { 28 | try { 29 | const { value, done } = await iterator.next(); 30 | if (done) { 31 | --this.iteratorCount; 32 | } else { 33 | this.yields.push({ iterator, value }); 34 | } 35 | } catch (e) { 36 | this.throws.push(e); 37 | } 38 | this.signal.resolve(); 39 | } 40 | 41 | async *iterate(): AsyncIterableIterator { 42 | while (this.iteratorCount > 0) { 43 | // Sleep until any of the wrapped iterators yields. 44 | await this.signal; 45 | 46 | // Note that while we're looping over `yields`, new items may be added. 47 | for (let i = 0; i < this.yields.length; i++) { 48 | const { iterator, value } = this.yields[i]; 49 | yield value; 50 | this.callIteratorNext(iterator); 51 | } 52 | 53 | if (this.throws.length) { 54 | for (const e of this.throws) { 55 | throw e; 56 | } 57 | this.throws.length = 0; 58 | } 59 | // Clear the `yields` list and reset the `signal` promise. 60 | this.yields.length = 0; 61 | this.signal = deferred(); 62 | } 63 | } 64 | 65 | [Symbol.asyncIterator](): AsyncIterableIterator { 66 | return this.iterate(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /std/async/pool.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /** 3 | * pooledMap transforms values from an (async) iterable into another async 4 | * iterable. The transforms are done concurrently, with a max concurrency 5 | * defined by the poolLimit. 6 | * 7 | * @param poolLimit The maximum count of items being processed concurrently. 8 | * @param array The input array for mapping. 9 | * @param iteratorFn The function to call for every item of the array. 10 | */ 11 | export function pooledMap( 12 | poolLimit: number, 13 | array: Iterable | AsyncIterable, 14 | iteratorFn: (data: T) => Promise 15 | ): AsyncIterableIterator { 16 | // Create the async iterable that is returned from this function. 17 | const res = new TransformStream, R>({ 18 | async transform( 19 | p: Promise, 20 | controller: TransformStreamDefaultController 21 | ): Promise { 22 | controller.enqueue(await p); 23 | }, 24 | }); 25 | // Start processing items from the iterator 26 | (async (): Promise => { 27 | const writer = res.writable.getWriter(); 28 | const executing: Array> = []; 29 | for await (const item of array) { 30 | const p = Promise.resolve().then(() => iteratorFn(item)); 31 | writer.write(p); 32 | const e: Promise = p.then(() => 33 | executing.splice(executing.indexOf(e), 1) 34 | ); 35 | executing.push(e); 36 | if (executing.length >= poolLimit) { 37 | await Promise.race(executing); 38 | } 39 | } 40 | // Wait until all ongoing events have processed, then close the writer. 41 | await Promise.all(executing); 42 | writer.close(); 43 | })(); 44 | return res.readable.getIterator(); 45 | } 46 | -------------------------------------------------------------------------------- /std/bytes/README.md: -------------------------------------------------------------------------------- 1 | # `bytes` 2 | 3 | The `bytes` module is made to provide helpers to manipulation of bytes slice. Ported from Deno's std. 4 | -------------------------------------------------------------------------------- /std/bytes/bytes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /** Find first index of binary pattern from a. If not found, then return -1 3 | * @param source source array 4 | * @param pat pattern to find in source array 5 | */ 6 | export function findIndex(source: Uint8Array, pat: Uint8Array): number { 7 | const s = pat[0]; 8 | for (let i = 0; i < source.length; i++) { 9 | if (source[i] !== s) continue; 10 | const pin = i; 11 | let matched = 1; 12 | let j = i; 13 | while (matched < pat.length) { 14 | j++; 15 | if (source[j] !== pat[j - pin]) { 16 | break; 17 | } 18 | matched++; 19 | } 20 | if (matched === pat.length) { 21 | return pin; 22 | } 23 | } 24 | return -1; 25 | } 26 | /** Find last index of binary pattern from a. If not found, then return -1. 27 | * @param source source array 28 | * @param pat pattern to find in source array 29 | */ 30 | export function findLastIndex(source: Uint8Array, pat: Uint8Array): number { 31 | const e = pat[pat.length - 1]; 32 | for (let i = source.length - 1; i >= 0; i--) { 33 | if (source[i] !== e) continue; 34 | const pin = i; 35 | let matched = 1; 36 | let j = i; 37 | while (matched < pat.length) { 38 | j--; 39 | if (source[j] !== pat[pat.length - 1 - (pin - j)]) { 40 | break; 41 | } 42 | matched++; 43 | } 44 | if (matched === pat.length) { 45 | return pin - pat.length + 1; 46 | } 47 | } 48 | return -1; 49 | } 50 | /** Check whether binary arrays are equal to each other. 51 | * @param source first array to check equality 52 | * @param match second array to check equality 53 | */ 54 | export function equal(source: Uint8Array, match: Uint8Array): boolean { 55 | if (source.length !== match.length) return false; 56 | for (let i = 0; i < match.length; i++) { 57 | if (source[i] !== match[i]) return false; 58 | } 59 | return true; 60 | } 61 | /** Check whether binary array starts with prefix. 62 | * @param source srouce array 63 | * @param prefix prefix array to check in source 64 | */ 65 | export function hasPrefix(source: Uint8Array, prefix: Uint8Array): boolean { 66 | for (let i = 0, max = prefix.length; i < max; i++) { 67 | if (source[i] !== prefix[i]) return false; 68 | } 69 | return true; 70 | } 71 | /** Check whether binary array ends with suffix. 72 | * @param source source array 73 | * @param suffix suffix array to check in source 74 | */ 75 | export function hasSuffix(source: Uint8Array, suffix: Uint8Array): boolean { 76 | for ( 77 | let srci = source.length - 1, sfxi = suffix.length - 1; 78 | sfxi >= 0; 79 | srci--, sfxi-- 80 | ) { 81 | if (source[srci] !== suffix[sfxi]) return false; 82 | } 83 | return true; 84 | } 85 | /** Repeat bytes. returns a new byte slice consisting of `count` copies of `b`. 86 | * @param origin The origin bytes 87 | * @param count The count you want to repeat. 88 | */ 89 | export function repeat(origin: Uint8Array, count: number): Uint8Array { 90 | if (count === 0) { 91 | return new Uint8Array(); 92 | } 93 | 94 | if (count < 0) { 95 | throw new Error("bytes: negative repeat count"); 96 | } else if ((origin.length * count) / count !== origin.length) { 97 | throw new Error("bytes: repeat count causes overflow"); 98 | } 99 | 100 | const int = Math.floor(count); 101 | 102 | if (int !== count) { 103 | throw new Error("bytes: repeat count must be an integer"); 104 | } 105 | 106 | const nb = new Uint8Array(origin.length * count); 107 | 108 | let bp = copyBytes(origin, nb); 109 | 110 | for (; bp < nb.length; bp *= 2) { 111 | copyBytes(nb.slice(0, bp), nb, bp); 112 | } 113 | 114 | return nb; 115 | } 116 | /** Concatenate two binary arrays and return new one. 117 | * @param origin origin array to concatenate 118 | * @param b array to concatenate with origin 119 | */ 120 | export function concat(origin: Uint8Array, b: Uint8Array): Uint8Array { 121 | const output = new Uint8Array(origin.length + b.length); 122 | output.set(origin, 0); 123 | output.set(b, origin.length); 124 | return output; 125 | } 126 | /** Check source array contains pattern array. 127 | * @param source source array 128 | * @param pat patter array 129 | */ 130 | export function contains(source: Uint8Array, pat: Uint8Array): boolean { 131 | return findIndex(source, pat) != -1; 132 | } 133 | /** 134 | * Copy bytes from one Uint8Array to another. Bytes from `src` which don't fit 135 | * into `dst` will not be copied. 136 | * 137 | * @param src Source byte array 138 | * @param dst Destination byte array 139 | * @param off Offset into `dst` at which to begin writing values from `src`. 140 | * @return number of bytes copied 141 | */ 142 | export function copyBytes(src: Uint8Array, dst: Uint8Array, off = 0): number { 143 | off = Math.max(0, Math.min(off, dst.byteLength)); 144 | const dstBytesAvailable = dst.byteLength - off; 145 | if (src.byteLength > dstBytesAvailable) { 146 | src = src.subarray(0, dstBytesAvailable); 147 | } 148 | dst.set(src, off); 149 | return src.byteLength; 150 | } 151 | -------------------------------------------------------------------------------- /std/encoding/hex.ts: -------------------------------------------------------------------------------- 1 | // Ported from Go 2 | // https://github.com/golang/go/blob/go1.12.5/src/encoding/hex/hex.go 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 7 | 8 | const hextable = new TextEncoder().encode("0123456789abcdef"); 9 | 10 | export function errInvalidByte(byte: number): Error { 11 | return new Error( 12 | "encoding/hex: invalid byte: " + 13 | new TextDecoder().decode(new Uint8Array([byte])) 14 | ); 15 | } 16 | 17 | export function errLength(): Error { 18 | return new Error("encoding/hex: odd length hex string"); 19 | } 20 | 21 | // fromHexChar converts a hex character into its value. 22 | function fromHexChar(byte: number): number { 23 | // '0' <= byte && byte <= '9' 24 | if (48 <= byte && byte <= 57) return byte - 48; 25 | // 'a' <= byte && byte <= 'f' 26 | if (97 <= byte && byte <= 102) return byte - 97 + 10; 27 | // 'A' <= byte && byte <= 'F' 28 | if (65 <= byte && byte <= 70) return byte - 65 + 10; 29 | 30 | throw errInvalidByte(byte); 31 | } 32 | /** 33 | * EncodedLen returns the length of an encoding of n source bytes. Specifically, 34 | * it returns n * 2. 35 | * @param n 36 | */ 37 | export function encodedLen(n: number): number { 38 | return n * 2; 39 | } 40 | /** 41 | * Encode encodes `src` into `encodedLen(src.length)` bytes. 42 | * @param src 43 | */ 44 | export function encode(src: Uint8Array): Uint8Array { 45 | const dst = new Uint8Array(encodedLen(src.length)); 46 | for (let i = 0; i < dst.length; i++) { 47 | const v = src[i]; 48 | dst[i * 2] = hextable[v >> 4]; 49 | dst[i * 2 + 1] = hextable[v & 0x0f]; 50 | } 51 | return dst; 52 | } 53 | /** 54 | * EncodeToString returns the hexadecimal encoding of `src`. 55 | * @param src 56 | */ 57 | export function encodeToString(src: Uint8Array): string { 58 | return new TextDecoder().decode(encode(src)); 59 | } 60 | /** 61 | * Decode decodes `src` into `decodedLen(src.length)` bytes 62 | * If the input is malformed an error will be thrown 63 | * the error. 64 | * @param src 65 | */ 66 | export function decode(src: Uint8Array): Uint8Array { 67 | const dst = new Uint8Array(decodedLen(src.length)); 68 | for (let i = 0; i < dst.length; i++) { 69 | const a = fromHexChar(src[i * 2]); 70 | const b = fromHexChar(src[i * 2 + 1]); 71 | dst[i] = (a << 4) | b; 72 | } 73 | 74 | if (src.length % 2 == 1) { 75 | // Check for invalid char before reporting bad length, 76 | // since the invalid char (if present) is an earlier problem. 77 | fromHexChar(src[dst.length * 2]); 78 | throw errLength(); 79 | } 80 | 81 | return dst; 82 | } 83 | /** 84 | * DecodedLen returns the length of decoding `x` source bytes. 85 | * Specifically, it returns `x / 2`. 86 | * @param x 87 | */ 88 | export function decodedLen(x: number): number { 89 | return x >>> 1; 90 | } 91 | /** 92 | * DecodeString returns the bytes represented by the hexadecimal string `s`. 93 | * DecodeString expects that src contains only hexadecimal characters and that 94 | * src has even length. 95 | * If the input is malformed, DecodeString will throw an error. 96 | * @param s the `string` to decode to `Uint8Array` 97 | */ 98 | export function decodeString(s: string): Uint8Array { 99 | return decode(new TextEncoder().encode(s)); 100 | } 101 | -------------------------------------------------------------------------------- /std/fmt/colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) elsa land 2020. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | const _red = "\u001b[31m"; 10 | const _black = "\u001b[30m"; 11 | const _green = "\u001b[32m"; 12 | const _yellow = "\u001b[33m"; 13 | const _blue = "\u001b[34m"; 14 | const _magenta = "\u001b[35m"; 15 | const _cyan = "\u001b[36m"; 16 | const _white = "\u001b[37m"; 17 | const _reset = "\u001b[0m"; 18 | 19 | // * 16bits colors 20 | const _brightBlack = "\u001b[30;1m"; 21 | const _brightRed = "\u001b[31;1m"; 22 | const _brightGreen = "\u001b[32;1m"; 23 | const _brightYellow = "\u001b[33;1m"; 24 | const _brightBlue = "\u001b[34;1m"; 25 | const _brightMagenta = "\u001b[35;1m"; 26 | const _brightCyan = "\u001b[36;1m"; 27 | const _brightWhite = "\u001b[37;1m"; 28 | 29 | // * backgroud color 30 | const _backgroundBlack = "\u001b[40m"; 31 | const _backgroundRed = "\u001b[41m"; 32 | const _backgroundGreen = "\u001b[42m"; 33 | const _backgroundYellow = "\u001b[43m"; 34 | const _backgroundBlue = "\u001b[44m"; 35 | const _backgroundMagenta = "\u001b[45m"; 36 | const _backgroundCyan = "\u001b[46m"; 37 | const _backgroundWhite = "\u001b[47m"; 38 | 39 | // * backgroud Bright color 40 | const _backgroundBrightBlack = "\u001b[40;1m"; 41 | const _backgroundBrightRed = "\u001b[41;1m"; 42 | const _backgroundBrightGreen = "\u001b[42;1m"; 43 | const _backgroundBrightYellow = "\u001b[43;1m"; 44 | const _backgroundBrightBlue = "\u001b[44;1m"; 45 | const _backgroundBrightMagenta = "\u001b[45;1m"; 46 | const _backgroundBrightCyan = "\u001b[46;1m"; 47 | const _backgroundBrightWhite = "\u001b[47;1m"; 48 | 49 | // * decorations 50 | const _bold = "\u001b[1m"; 51 | const _underline = "\u001b[4m"; 52 | const _reversed = "\u001b[7m"; 53 | 54 | /** 55 | * 8bits red color 56 | * @param text 57 | */ 58 | export function red(text: string) { 59 | return `${_red}${text}${_reset}`; 60 | } 61 | 62 | /** 63 | * 8bits black color 64 | * @param text 65 | */ 66 | export function black(text: string) { 67 | return `${_black}${text}${_reset}`; 68 | } 69 | 70 | /** 71 | * 8bits green color 72 | * @param text 73 | */ 74 | export function green(text: string) { 75 | return `${_green}${text}${_reset}`; 76 | } 77 | 78 | /** 79 | * 8bits yellow color 80 | * @param text 81 | */ 82 | export function yellow(text: string) { 83 | return `${_yellow}${text}${_reset}`; 84 | } 85 | 86 | /** 87 | * 8bits blue color 88 | * @param text 89 | */ 90 | export function blue(text: string) { 91 | return `${_blue}${text}${_reset}`; 92 | } 93 | 94 | /** 95 | * 8bits magenta color 96 | * @param text 97 | */ 98 | export function magenta(text: string) { 99 | return `${_magenta}${text}${_reset}`; 100 | } 101 | 102 | /** 103 | * 8bits cyan color 104 | * @param text 105 | */ 106 | export function cyan(text: string) { 107 | return `${_cyan}${text}${_reset}`; 108 | } 109 | 110 | /** 111 | * 8bits white color 112 | * @param text 113 | */ 114 | export function white(text: string) { 115 | return `${_white}${text}${_reset}`; 116 | } 117 | 118 | /** 119 | * brightBlack color 120 | * @param text 121 | */ 122 | export function brightBlack(text: string) { 123 | return `${_brightBlack}${text}${_reset}`; 124 | } 125 | 126 | /** 127 | * brightRed color 128 | * @param text 129 | */ 130 | export function brightRed(text: string) { 131 | return `${_brightRed}${text}${_reset}`; 132 | } 133 | 134 | /** 135 | * brightGreen color 136 | * @param text 137 | */ 138 | export function brightGreen(text: string) { 139 | return `${_brightGreen}${text}${_reset}`; 140 | } 141 | 142 | /** 143 | * brightYellow color 144 | * @param text 145 | */ 146 | export function brightYellow(text: string) { 147 | return `${_brightYellow}${text}${_reset}`; 148 | } 149 | 150 | /** 151 | * brightBlue color 152 | * @param text 153 | */ 154 | export function brightBlue(text: string) { 155 | return `${_brightBlue}${text}${_reset}`; 156 | } 157 | 158 | /** 159 | * brightMagenta color 160 | * @param text 161 | */ 162 | export function brightMagenta(text: string) { 163 | return `${_brightMagenta}${text}${_reset}`; 164 | } 165 | 166 | /** 167 | * brightCyan color 168 | * @param text 169 | */ 170 | export function brightCyan(text: string) { 171 | return `${_brightCyan}${text}${_reset}`; 172 | } 173 | 174 | /** 175 | * brightWhite color 176 | * @param text 177 | */ 178 | export function brightWhite(text: string) { 179 | return `${_brightWhite}${text}${_reset}`; 180 | } 181 | 182 | /** 183 | * backgroundBlack color 184 | * @param text 185 | */ 186 | export function backgroundBlack(text: string) { 187 | return `${_backgroundBlack}${text}${_reset}`; 188 | } 189 | 190 | /** 191 | * backgroundRed color 192 | * @param text 193 | */ 194 | export function backgroundRed(text: string) { 195 | return `${_backgroundRed}${text}${_reset}`; 196 | } 197 | 198 | /** 199 | * backgroundGreen color 200 | * @param text 201 | */ 202 | export function backgroundGreen(text: string) { 203 | return `${_backgroundGreen}${text}${_reset}`; 204 | } 205 | 206 | /** 207 | * backgroundYellow color 208 | * @param text 209 | */ 210 | export function backgroundYellow(text: string) { 211 | return `${_backgroundYellow}${text}${_reset}`; 212 | } 213 | 214 | /** 215 | * backgroundBlue color 216 | * @param text 217 | */ 218 | export function backgroundBlue(text: string) { 219 | return `${_backgroundBlue}${text}${_reset}`; 220 | } 221 | 222 | /** 223 | * backgroundMagenta color 224 | * @param text 225 | */ 226 | export function backgroundMagenta(text: string) { 227 | return `${_backgroundMagenta}${text}${_reset}`; 228 | } 229 | 230 | /** 231 | * backgroundCyan color 232 | * @param text 233 | */ 234 | export function backgroundCyan(text: string) { 235 | return `${_backgroundCyan}${text}${_reset}`; 236 | } 237 | 238 | /** 239 | * backgroundWhite color 240 | * @param text 241 | */ 242 | export function backgroundWhite(text: string) { 243 | return `${_backgroundWhite}${text}${_reset}`; 244 | } 245 | 246 | /** 247 | * backgroundBrightBlack color 248 | * @param text 249 | */ 250 | export function backgroundBrightBlack(text: string) { 251 | return `${_backgroundBrightBlack}${text}${_reset}`; 252 | } 253 | 254 | /** 255 | * backgroundBrightRed color 256 | * @param text 257 | */ 258 | export function backgroundBrightRed(text: string) { 259 | return `${_backgroundBrightRed}${text}${_reset}`; 260 | } 261 | 262 | /** 263 | * backgroundBrightGreen color 264 | * @param text 265 | */ 266 | export function backgroundBrightGreen(text: string) { 267 | return `${_backgroundBrightGreen}${text}${_reset}`; 268 | } 269 | 270 | /** 271 | * backgroundBrightYellow color 272 | * @param text 273 | */ 274 | export function backgroundBrightYellow(text: string) { 275 | return `${_backgroundBrightYellow}${text}${_reset}`; 276 | } 277 | 278 | /** 279 | * backgroundBrightBlue color 280 | * @param text 281 | */ 282 | export function backgroundBrightBlue(text: string) { 283 | return `${_backgroundBrightBlue}${text}${_reset}`; 284 | } 285 | 286 | /** 287 | * backgroundBrightMagenta color 288 | * @param text 289 | */ 290 | export function backgroundBrightMagenta(text: string) { 291 | return `${_backgroundBrightMagenta}${text}${_reset}`; 292 | } 293 | 294 | /** 295 | * backgroundBrightCyan color 296 | * @param text 297 | */ 298 | export function backgroundBrightCyan(text: string) { 299 | return `${_backgroundBrightCyan}${text}${_reset}`; 300 | } 301 | 302 | /** 303 | * backgroundBrightWhite color 304 | * @param text 305 | */ 306 | export function backgroundBrightWhite(text: string) { 307 | return `${_backgroundBrightWhite}${text}${_reset}`; 308 | } 309 | 310 | /** 311 | * bold text 312 | * @param text 313 | */ 314 | export function bold(text: string) { 315 | return `${_bold}${text}${_reset}`; 316 | } 317 | 318 | /** 319 | * underline text 320 | * @param text 321 | */ 322 | export function underline(text: string) { 323 | return `${_underline}${text}${_reset}`; 324 | } 325 | 326 | /** 327 | * reverse color text 328 | * @param text 329 | */ 330 | export function reversed(text: string) { 331 | return `${_reversed}${text}${_reset}`; 332 | } 333 | 334 | /** 335 | * ANSI 256 color text 336 | * @param text 337 | */ 338 | export function color256(text: string, code: number) { 339 | if (code < 0 || code > 255) { 340 | console.log( 341 | red( 342 | bold( 343 | "only numbers between 0 and 255 can be passed in the color256 functio" 344 | ) 345 | ) 346 | ); 347 | throw new Error(); 348 | } 349 | 350 | return `\u001b[38;5;${code}m${text}${_reset}`; 351 | } 352 | -------------------------------------------------------------------------------- /std/fs/exist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) elsa land 2020. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | -------------------------------------------------------------------------------- /std/hash/aes.ts: -------------------------------------------------------------------------------- 1 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 2 | /* AES implementation in JavaScript (c) Chris Veness 2005-2016 */ 3 | /* MIT Licence */ 4 | /* www.movable-type.co.uk/scripts/aes.html */ 5 | /* - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - */ 6 | 7 | /** 8 | * Copyright (c) elsa land 2020. 9 | * 10 | * This source code is licensed under the MIT license found in the 11 | * LICENSE file in the root directory of this source tree. 12 | * 13 | */ 14 | 15 | /** 16 | * number of valid bits 17 | */ 18 | type BitSize = "128" | "192" | "256"; 19 | 20 | /** 21 | * implementation of AES ( Advanced Encryption Standard ) in TypeScript 22 | */ 23 | 24 | export class AES { 25 | private static sBox = [ 26 | 0x63, 27 | 0x7c, 28 | 0x77, 29 | 0x7b, 30 | 0xf2, 31 | 0x6b, 32 | 0x6f, 33 | 0xc5, 34 | 0x30, 35 | 0x01, 36 | 0x67, 37 | 0x2b, 38 | 0xfe, 39 | 0xd7, 40 | 0xab, 41 | 0x76, 42 | 0xca, 43 | 0x82, 44 | 0xc9, 45 | 0x7d, 46 | 0xfa, 47 | 0x59, 48 | 0x47, 49 | 0xf0, 50 | 0xad, 51 | 0xd4, 52 | 0xa2, 53 | 0xaf, 54 | 0x9c, 55 | 0xa4, 56 | 0x72, 57 | 0xc0, 58 | 0xb7, 59 | 0xfd, 60 | 0x93, 61 | 0x26, 62 | 0x36, 63 | 0x3f, 64 | 0xf7, 65 | 0xcc, 66 | 0x34, 67 | 0xa5, 68 | 0xe5, 69 | 0xf1, 70 | 0x71, 71 | 0xd8, 72 | 0x31, 73 | 0x15, 74 | 0x04, 75 | 0xc7, 76 | 0x23, 77 | 0xc3, 78 | 0x18, 79 | 0x96, 80 | 0x05, 81 | 0x9a, 82 | 0x07, 83 | 0x12, 84 | 0x80, 85 | 0xe2, 86 | 0xeb, 87 | 0x27, 88 | 0xb2, 89 | 0x75, 90 | 0x09, 91 | 0x83, 92 | 0x2c, 93 | 0x1a, 94 | 0x1b, 95 | 0x6e, 96 | 0x5a, 97 | 0xa0, 98 | 0x52, 99 | 0x3b, 100 | 0xd6, 101 | 0xb3, 102 | 0x29, 103 | 0xe3, 104 | 0x2f, 105 | 0x84, 106 | 0x53, 107 | 0xd1, 108 | 0x00, 109 | 0xed, 110 | 0x20, 111 | 0xfc, 112 | 0xb1, 113 | 0x5b, 114 | 0x6a, 115 | 0xcb, 116 | 0xbe, 117 | 0x39, 118 | 0x4a, 119 | 0x4c, 120 | 0x58, 121 | 0xcf, 122 | 0xd0, 123 | 0xef, 124 | 0xaa, 125 | 0xfb, 126 | 0x43, 127 | 0x4d, 128 | 0x33, 129 | 0x85, 130 | 0x45, 131 | 0xf9, 132 | 0x02, 133 | 0x7f, 134 | 0x50, 135 | 0x3c, 136 | 0x9f, 137 | 0xa8, 138 | 0x51, 139 | 0xa3, 140 | 0x40, 141 | 0x8f, 142 | 0x92, 143 | 0x9d, 144 | 0x38, 145 | 0xf5, 146 | 0xbc, 147 | 0xb6, 148 | 0xda, 149 | 0x21, 150 | 0x10, 151 | 0xff, 152 | 0xf3, 153 | 0xd2, 154 | 0xcd, 155 | 0x0c, 156 | 0x13, 157 | 0xec, 158 | 0x5f, 159 | 0x97, 160 | 0x44, 161 | 0x17, 162 | 0xc4, 163 | 0xa7, 164 | 0x7e, 165 | 0x3d, 166 | 0x64, 167 | 0x5d, 168 | 0x19, 169 | 0x73, 170 | 0x60, 171 | 0x81, 172 | 0x4f, 173 | 0xdc, 174 | 0x22, 175 | 0x2a, 176 | 0x90, 177 | 0x88, 178 | 0x46, 179 | 0xee, 180 | 0xb8, 181 | 0x14, 182 | 0xde, 183 | 0x5e, 184 | 0x0b, 185 | 0xdb, 186 | 0xe0, 187 | 0x32, 188 | 0x3a, 189 | 0x0a, 190 | 0x49, 191 | 0x06, 192 | 0x24, 193 | 0x5c, 194 | 0xc2, 195 | 0xd3, 196 | 0xac, 197 | 0x62, 198 | 0x91, 199 | 0x95, 200 | 0xe4, 201 | 0x79, 202 | 0xe7, 203 | 0xc8, 204 | 0x37, 205 | 0x6d, 206 | 0x8d, 207 | 0xd5, 208 | 0x4e, 209 | 0xa9, 210 | 0x6c, 211 | 0x56, 212 | 0xf4, 213 | 0xea, 214 | 0x65, 215 | 0x7a, 216 | 0xae, 217 | 0x08, 218 | 0xba, 219 | 0x78, 220 | 0x25, 221 | 0x2e, 222 | 0x1c, 223 | 0xa6, 224 | 0xb4, 225 | 0xc6, 226 | 0xe8, 227 | 0xdd, 228 | 0x74, 229 | 0x1f, 230 | 0x4b, 231 | 0xbd, 232 | 0x8b, 233 | 0x8a, 234 | 0x70, 235 | 0x3e, 236 | 0xb5, 237 | 0x66, 238 | 0x48, 239 | 0x03, 240 | 0xf6, 241 | 0x0e, 242 | 0x61, 243 | 0x35, 244 | 0x57, 245 | 0xb9, 246 | 0x86, 247 | 0xc1, 248 | 0x1d, 249 | 0x9e, 250 | 0xe1, 251 | 0xf8, 252 | 0x98, 253 | 0x11, 254 | 0x69, 255 | 0xd9, 256 | 0x8e, 257 | 0x94, 258 | 0x9b, 259 | 0x1e, 260 | 0x87, 261 | 0xe9, 262 | 0xce, 263 | 0x55, 264 | 0x28, 265 | 0xdf, 266 | 0x8c, 267 | 0xa1, 268 | 0x89, 269 | 0x0d, 270 | 0xbf, 271 | 0xe6, 272 | 0x42, 273 | 0x68, 274 | 0x41, 275 | 0x99, 276 | 0x2d, 277 | 0x0f, 278 | 0xb0, 279 | 0x54, 280 | 0xbb, 281 | 0x16, 282 | ]; 283 | 284 | private static rCon = [ 285 | [0x00, 0x00, 0x00, 0x00], 286 | [0x01, 0x00, 0x00, 0x00], 287 | [0x02, 0x00, 0x00, 0x00], 288 | [0x04, 0x00, 0x00, 0x00], 289 | [0x08, 0x00, 0x00, 0x00], 290 | [0x10, 0x00, 0x00, 0x00], 291 | [0x20, 0x00, 0x00, 0x00], 292 | [0x40, 0x00, 0x00, 0x00], 293 | [0x80, 0x00, 0x00, 0x00], 294 | [0x1b, 0x00, 0x00, 0x00], 295 | [0x36, 0x00, 0x00, 0x00], 296 | ]; 297 | private static cipher(input: any, w: any) { 298 | const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) 299 | const Nr = w.length / Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys 300 | 301 | let state = [[], [], [], []] as any[][]; // initialise 4xNb byte-array 'state' with input [§3.4] 302 | 303 | for (let i = 0; i < 4 * Nb; i++) { 304 | state[i % 4][Math.floor(i / 4)] = input[i]; 305 | } 306 | 307 | state = AES.addRoundKey(state, w, 0, Nb); 308 | 309 | for (let round = 1; round < Nr; round++) { 310 | state = AES.subBytes(state, Nb); 311 | state = AES.shiftRows(state, Nb); 312 | state = AES.mixColumns(state, Nb); 313 | state = AES.addRoundKey(state, w, round, Nb); 314 | } 315 | 316 | state = AES.subBytes(state, Nb); 317 | state = AES.shiftRows(state, Nb); 318 | state = AES.addRoundKey(state, w, Nr, Nb); 319 | 320 | const output = new Array(4 * Nb); // convert state to 1-d array before returning [§3.4] 321 | for (let i = 0; i < 4 * Nb; i++) 322 | output[i] = state[i % 4][Math.floor(i / 4)]; 323 | 324 | return output; 325 | } 326 | 327 | private static keyExpansion(key: any) { 328 | const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) 329 | const Nk = key.length / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys 330 | const Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys 331 | 332 | const w = new Array(Nb * (Nr + 1)); 333 | let temp = new Array(4); 334 | 335 | // initialize first Nk words of expanded key with cipher key 336 | for (let i = 0; i < Nk; i++) { 337 | const r = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]; 338 | w[i] = r; 339 | } 340 | 341 | // expand the key into the remainder of the schedule 342 | for (let i = Nk; i < Nb * (Nr + 1); i++) { 343 | w[i] = new Array(4); 344 | for (let t = 0; t < 4; t++) { 345 | temp[t] = w[i - 1][t]; 346 | } 347 | // each Nk'th word has extra transformation 348 | if (i % Nk == 0) { 349 | temp = AES.subWord(AES.rotWord(temp)); 350 | for (let t = 0; t < 4; t++) { 351 | temp[t] ^= AES.rCon[i / Nk][t]; 352 | } 353 | } 354 | // 256-bit key has subWord applied every 4th word 355 | else if (Nk > 6 && i % Nk == 4) { 356 | temp = AES.subWord(temp); 357 | } 358 | // xor w[i] with w[i-1] and w[i-Nk] 359 | for (let t = 0; t < 4; t++) { 360 | w[i][t] = w[i - Nk][t] ^ temp[t]; 361 | } 362 | } 363 | 364 | return w; 365 | } 366 | 367 | private static subBytes(s: any, Nb: any) { 368 | for (let r = 0; r < 4; r++) { 369 | for (let c = 0; c < Nb; c++) { 370 | s[r][c] = AES.sBox[s[r][c]]; 371 | } 372 | } 373 | return s; 374 | } 375 | 376 | private static shiftRows(s: any, Nb: any) { 377 | const t = new Array(4); 378 | for (let r = 1; r < 4; r++) { 379 | for (let c = 0; c < 4; c++) { 380 | t[c] = s[r][(c + r) % Nb]; 381 | } // shift into temp copy 382 | for (let c = 0; c < 4; c++) { 383 | s[r][c] = t[c]; 384 | } // and copy back 385 | } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES): 386 | return s; // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf 387 | } 388 | 389 | private static mixColumns(s: any) { 390 | for (let c = 0; c < 4; c++) { 391 | const a = new Array(4); // 'a' is a copy of the current column from 's' 392 | const b = new Array(4); // 'b' is a•{02} in GF(2^8) 393 | for (let i = 0; i < 4; i++) { 394 | a[i] = s[i][c]; 395 | b[i] = s[i][c] & 0x80 ? (s[i][c] << 1) ^ 0x011b : s[i][c] << 1; 396 | } 397 | // a[n] ^ b[n] is a•{03} in GF(2^8) 398 | s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // {02}•a0 + {03}•a1 + a2 + a3 399 | s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 • {02}•a1 + {03}•a2 + a3 400 | s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + {02}•a2 + {03}•a3 401 | s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // {03}•a0 + a1 + a2 + {02}•a3 402 | } 403 | return s; 404 | } 405 | 406 | private static addRoundKey(state: any, w: any, rnd: any, Nb: any) { 407 | for (let r = 0; r < 4; r++) { 408 | for (let c = 0; c < Nb; c++) { 409 | state[r][c] ^= w[rnd * 4 + c][r]; 410 | } 411 | } 412 | return state; 413 | } 414 | 415 | private static subWord(w: any[]) { 416 | for (let i = 0; i < 4; i++) { 417 | w[i] = AES.sBox[w[i]]; 418 | } 419 | return w; 420 | } 421 | 422 | private static rotWord(w: any[]) { 423 | const tmp = w[0]; 424 | for (let i = 0; i < 3; i++) { 425 | w[i] = w[i + 1]; 426 | } 427 | w[3] = tmp; 428 | return w; 429 | } 430 | 431 | private static base64Decode(text: string) { 432 | return atob(text); 433 | } 434 | 435 | private static base64Encode(text: string) { 436 | return btoa(text); 437 | } 438 | 439 | private static utf8Encode(text: string) { 440 | return unescape(encodeURIComponent(text)); 441 | } 442 | 443 | private static utf8Decode(text: string) { 444 | try { 445 | return decodeURIComponent(escape(text)); 446 | } catch (err) { 447 | return text; 448 | } 449 | } 450 | 451 | public static get Ctr() { 452 | return { 453 | /** 454 | * 455 | * @param ciphertext - encrypted text 456 | * @param password - encryption password 457 | * @param nBits - bits size 458 | *```ts 459 | * const Text = AES.Ctr.decrypt("uAIyZlqGe18XRt2+akj3wzCKbhtcINuC3RItd0U=", "elsa land", "128"); 460 | * 461 | * console.log(Text); // hello world from elsa 462 | * ``` 463 | */ 464 | decrypt(ciphertext: string, password: string, nBits: BitSize) { 465 | const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 466 | 467 | if ( 468 | !( 469 | parseInt(nBits) === 128 || 470 | parseInt(nBits) === 192 || 471 | parseInt(nBits) === 256 472 | ) 473 | ) { 474 | throw new Error("Key size is not 128 / 192 / 256"); 475 | } 476 | 477 | ciphertext = AES.base64Decode(ciphertext); 478 | password = AES.utf8Encode(password); 479 | 480 | // use AES to encrypt password (mirroring encrypt routine) 481 | const nBytes = parseInt(nBits) / 8; // no bytes in key 482 | const pwBytes = new Array(nBytes); 483 | for (let i = 0; i < nBytes; i++) { 484 | pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0; 485 | } 486 | let key = AES.cipher(pwBytes, AES.keyExpansion(pwBytes)); 487 | key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long 488 | 489 | // recover nonce from 1st 8 bytes of ciphertext 490 | const counterBlock = new Array(8); 491 | const ctrTxt = ciphertext.slice(0, 8); 492 | for (let i = 0; i < 8; i++) { 493 | counterBlock[i] = ctrTxt.charCodeAt(i); 494 | } 495 | 496 | // generate key schedule 497 | const keySchedule = AES.keyExpansion(key); 498 | 499 | // separate ciphertext into blocks (skipping past initial 8 bytes) 500 | const nBlocks = Math.ceil((ciphertext.length - 8) / blockSize); 501 | const ct = new Array(nBlocks); 502 | for (let b = 0; b < nBlocks; b++) 503 | ct[b] = ciphertext.slice( 504 | 8 + b * blockSize, 505 | 8 + b * blockSize + blockSize 506 | ); 507 | ((ciphertext as unknown) as any[]) = ct; // ciphertext is now array of block-length strings 508 | 509 | // plaintext will get generated block-by-block into array of block-length strings 510 | let plaintext = ""; 511 | 512 | for (let b = 0; b < nBlocks; b++) { 513 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 514 | for (let c = 0; c < 4; c++) { 515 | counterBlock[15 - c] = (b >>> (c * 8)) & 0xff; 516 | } 517 | for (let c = 0; c < 4; c++) { 518 | counterBlock[15 - c - 4] = 519 | (((b + 1) / 0x100000000 - 1) >>> (c * 8)) & 0xff; 520 | } 521 | 522 | const cipherCntr = AES.cipher(counterBlock, keySchedule); // encrypt counter block 523 | 524 | const plaintxtByte = new Array(ciphertext[b].length); 525 | for (let i = 0; i < ciphertext[b].length; i++) { 526 | // -- xor plaintext with ciphered counter byte-by-byte -- 527 | plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i); 528 | plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]); 529 | } 530 | plaintext += plaintxtByte.join(""); 531 | 532 | // if within web worker, announce progress every 1000 blocks (roughly every 50ms) 533 | } 534 | 535 | plaintext = AES.utf8Decode(plaintext); // decode from UTF8 back to Unicode multi-byte chars 536 | 537 | return plaintext; 538 | }, 539 | 540 | /** 541 | * 542 | * @param plaintext - text to encrypt 543 | * @param password - encryption password 544 | * @param nBits - bits size 545 | *```ts 546 | * const Text = AES.Ctr.decrypt("hello world from elsa", "elsa land", "128"); 547 | * 548 | * console.log(Text); // uAIyZlqGe18XRt2+akj3wzCKbhtcINuC3RItd0U= 549 | * ``` 550 | */ 551 | encrypt(plaintext: string, password: string, nBits: BitSize) { 552 | const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 553 | if ( 554 | !( 555 | parseInt(nBits) === 128 || 556 | parseInt(nBits) === 192 || 557 | parseInt(nBits) === 256 558 | ) 559 | ) { 560 | throw new Error("Key size is not 128 / 192 / 256"); 561 | } 562 | plaintext = AES.utf8Encode(plaintext); 563 | password = AES.utf8Encode(password); 564 | 565 | // use AES itself to encrypt password to get cipher key (using plain password as source for key 566 | // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use) 567 | const nBytes = parseInt(nBits) / 8; // no bytes in key (16/24/32) 568 | const pwBytes = new Array(nBytes); 569 | for (let i = 0; i < nBytes; i++) { 570 | // use 1st 16/24/32 chars of password for key 571 | pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0; 572 | } 573 | let key = AES.cipher(pwBytes, AES.keyExpansion(pwBytes)); // gives us 16-byte key 574 | key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long 575 | 576 | // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec, 577 | // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106 578 | const counterBlock = new Array(blockSize); 579 | 580 | const nonce = new Date().getTime(); // timestamp: milliseconds since 1-Jan-1970 581 | const nonceMs = nonce % 1000; 582 | const nonceSec = Math.floor(nonce / 1000); 583 | const nonceRnd = Math.floor(Math.random() * 0xffff); 584 | // for debugging: nonce = nonceMs = nonceSec = nonceRnd = 0; 585 | 586 | for (let i = 0; i < 2; i++) { 587 | counterBlock[i] = (nonceMs >>> (i * 8)) & 0xff; 588 | } 589 | for (let i = 0; i < 2; i++) { 590 | counterBlock[i + 2] = (nonceRnd >>> (i * 8)) & 0xff; 591 | } 592 | for (let i = 0; i < 4; i++) { 593 | counterBlock[i + 4] = (nonceSec >>> (i * 8)) & 0xff; 594 | } 595 | 596 | // and convert it to a string to go on the front of the ciphertext 597 | let ctrTxt = ""; 598 | for (let i = 0; i < 8; i++) { 599 | ctrTxt += String.fromCharCode(counterBlock[i]); 600 | } 601 | 602 | // generate key schedule - an expansion of the key into distinct Key Rounds for each round 603 | const keySchedule = AES.keyExpansion(key); 604 | 605 | const blockCount = Math.ceil(plaintext.length / blockSize); 606 | let ciphertext = ""; 607 | 608 | for (let b = 0; b < blockCount; b++) { 609 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 610 | // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) 611 | for (let c = 0; c < 4; c++) { 612 | counterBlock[15 - c] = (b >>> (c * 8)) & 0xff; 613 | } 614 | for (let c = 0; c < 4; c++) { 615 | counterBlock[15 - c - 4] = (b / 0x100000000) >>> (c * 8); 616 | } 617 | 618 | const cipherCntr = AES.cipher(counterBlock, keySchedule); // -- encrypt counter block -- 619 | 620 | // block size is reduced on final block 621 | const blockLength = 622 | b < blockCount - 1 623 | ? blockSize 624 | : ((plaintext.length - 1) % blockSize) + 1; 625 | const cipherChar = new Array(blockLength); 626 | 627 | for (let i = 0; i < blockLength; i++) { 628 | // -- xor plaintext with ciphered counter char-by-char -- 629 | cipherChar[i] = 630 | cipherCntr[i] ^ plaintext.charCodeAt(b * blockSize + i); 631 | cipherChar[i] = String.fromCharCode(cipherChar[i]); 632 | } 633 | ciphertext += cipherChar.join(""); 634 | 635 | // if within web worker, announce progress every 1000 blocks (roughly every 50ms) 636 | } 637 | 638 | ciphertext = AES.base64Encode(ctrTxt + ciphertext); 639 | 640 | return ciphertext; 641 | }, 642 | }; 643 | } 644 | } 645 | -------------------------------------------------------------------------------- /std/hash/hasher.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | 3 | export type Message = string | ArrayBuffer; 4 | export type OutputFormat = "hex" | "base64"; 5 | 6 | export interface Hasher { 7 | update(data: Message): this; 8 | digest(): ArrayBuffer; 9 | toString(format?: OutputFormat): string; 10 | } 11 | -------------------------------------------------------------------------------- /std/hash/md5.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | 3 | import * as hex from "../encoding/hex.ts"; 4 | 5 | const TYPE_ERROR_MSG = "md5: `data` is invalid type"; 6 | const BLOCK_SIZE = 64; 7 | 8 | export type Message = string | ArrayBuffer; 9 | /** Md5 hash */ 10 | export class Md5 { 11 | #a: number; 12 | #b: number; 13 | #c: number; 14 | #d: number; 15 | #block: Uint8Array; 16 | #pos: number; 17 | #n0: number; 18 | #n1: number; 19 | 20 | constructor() { 21 | this.#a = 0x67452301; 22 | this.#b = 0xefcdab89; 23 | this.#c = 0x98badcfe; 24 | this.#d = 0x10325476; 25 | this.#block = new Uint8Array(BLOCK_SIZE); 26 | this.#pos = 0; 27 | this.#n0 = 0; 28 | this.#n1 = 0; 29 | } 30 | 31 | private addLength(len: number): void { 32 | let n0 = this.#n0; 33 | n0 += len; 34 | if (n0 > 0xffffffff) this.#n1 += 1; 35 | this.#n0 = n0 >>> 0; 36 | } 37 | 38 | private hash(block: Uint8Array): void { 39 | let a = this.#a; 40 | let b = this.#b; 41 | let c = this.#c; 42 | let d = this.#d; 43 | 44 | const blk = (i: number): number => 45 | block[i] | 46 | (block[i + 1] << 8) | 47 | (block[i + 2] << 16) | 48 | (block[i + 3] << 24); 49 | 50 | const rol32 = (x: number, n: number): number => (x << n) | (x >>> (32 - n)); 51 | 52 | const x0 = blk(0); 53 | const x1 = blk(4); 54 | const x2 = blk(8); 55 | const x3 = blk(12); 56 | const x4 = blk(16); 57 | const x5 = blk(20); 58 | const x6 = blk(24); 59 | const x7 = blk(28); 60 | const x8 = blk(32); 61 | const x9 = blk(36); 62 | const xa = blk(40); 63 | const xb = blk(44); 64 | const xc = blk(48); 65 | const xd = blk(52); 66 | const xe = blk(56); 67 | const xf = blk(60); 68 | 69 | // round 1 70 | a = b + rol32((((c ^ d) & b) ^ d) + a + x0 + 0xd76aa478, 7); 71 | d = a + rol32((((b ^ c) & a) ^ c) + d + x1 + 0xe8c7b756, 12); 72 | c = d + rol32((((a ^ b) & d) ^ b) + c + x2 + 0x242070db, 17); 73 | b = c + rol32((((d ^ a) & c) ^ a) + b + x3 + 0xc1bdceee, 22); 74 | a = b + rol32((((c ^ d) & b) ^ d) + a + x4 + 0xf57c0faf, 7); 75 | d = a + rol32((((b ^ c) & a) ^ c) + d + x5 + 0x4787c62a, 12); 76 | c = d + rol32((((a ^ b) & d) ^ b) + c + x6 + 0xa8304613, 17); 77 | b = c + rol32((((d ^ a) & c) ^ a) + b + x7 + 0xfd469501, 22); 78 | a = b + rol32((((c ^ d) & b) ^ d) + a + x8 + 0x698098d8, 7); 79 | d = a + rol32((((b ^ c) & a) ^ c) + d + x9 + 0x8b44f7af, 12); 80 | c = d + rol32((((a ^ b) & d) ^ b) + c + xa + 0xffff5bb1, 17); 81 | b = c + rol32((((d ^ a) & c) ^ a) + b + xb + 0x895cd7be, 22); 82 | a = b + rol32((((c ^ d) & b) ^ d) + a + xc + 0x6b901122, 7); 83 | d = a + rol32((((b ^ c) & a) ^ c) + d + xd + 0xfd987193, 12); 84 | c = d + rol32((((a ^ b) & d) ^ b) + c + xe + 0xa679438e, 17); 85 | b = c + rol32((((d ^ a) & c) ^ a) + b + xf + 0x49b40821, 22); 86 | 87 | // round 2 88 | a = b + rol32((((b ^ c) & d) ^ c) + a + x1 + 0xf61e2562, 5); 89 | d = a + rol32((((a ^ b) & c) ^ b) + d + x6 + 0xc040b340, 9); 90 | c = d + rol32((((d ^ a) & b) ^ a) + c + xb + 0x265e5a51, 14); 91 | b = c + rol32((((c ^ d) & a) ^ d) + b + x0 + 0xe9b6c7aa, 20); 92 | a = b + rol32((((b ^ c) & d) ^ c) + a + x5 + 0xd62f105d, 5); 93 | d = a + rol32((((a ^ b) & c) ^ b) + d + xa + 0x02441453, 9); 94 | c = d + rol32((((d ^ a) & b) ^ a) + c + xf + 0xd8a1e681, 14); 95 | b = c + rol32((((c ^ d) & a) ^ d) + b + x4 + 0xe7d3fbc8, 20); 96 | a = b + rol32((((b ^ c) & d) ^ c) + a + x9 + 0x21e1cde6, 5); 97 | d = a + rol32((((a ^ b) & c) ^ b) + d + xe + 0xc33707d6, 9); 98 | c = d + rol32((((d ^ a) & b) ^ a) + c + x3 + 0xf4d50d87, 14); 99 | b = c + rol32((((c ^ d) & a) ^ d) + b + x8 + 0x455a14ed, 20); 100 | a = b + rol32((((b ^ c) & d) ^ c) + a + xd + 0xa9e3e905, 5); 101 | d = a + rol32((((a ^ b) & c) ^ b) + d + x2 + 0xfcefa3f8, 9); 102 | c = d + rol32((((d ^ a) & b) ^ a) + c + x7 + 0x676f02d9, 14); 103 | b = c + rol32((((c ^ d) & a) ^ d) + b + xc + 0x8d2a4c8a, 20); 104 | 105 | // round 3 106 | a = b + rol32((b ^ c ^ d) + a + x5 + 0xfffa3942, 4); 107 | d = a + rol32((a ^ b ^ c) + d + x8 + 0x8771f681, 11); 108 | c = d + rol32((d ^ a ^ b) + c + xb + 0x6d9d6122, 16); 109 | b = c + rol32((c ^ d ^ a) + b + xe + 0xfde5380c, 23); 110 | a = b + rol32((b ^ c ^ d) + a + x1 + 0xa4beea44, 4); 111 | d = a + rol32((a ^ b ^ c) + d + x4 + 0x4bdecfa9, 11); 112 | c = d + rol32((d ^ a ^ b) + c + x7 + 0xf6bb4b60, 16); 113 | b = c + rol32((c ^ d ^ a) + b + xa + 0xbebfbc70, 23); 114 | a = b + rol32((b ^ c ^ d) + a + xd + 0x289b7ec6, 4); 115 | d = a + rol32((a ^ b ^ c) + d + x0 + 0xeaa127fa, 11); 116 | c = d + rol32((d ^ a ^ b) + c + x3 + 0xd4ef3085, 16); 117 | b = c + rol32((c ^ d ^ a) + b + x6 + 0x04881d05, 23); 118 | a = b + rol32((b ^ c ^ d) + a + x9 + 0xd9d4d039, 4); 119 | d = a + rol32((a ^ b ^ c) + d + xc + 0xe6db99e5, 11); 120 | c = d + rol32((d ^ a ^ b) + c + xf + 0x1fa27cf8, 16); 121 | b = c + rol32((c ^ d ^ a) + b + x2 + 0xc4ac5665, 23); 122 | 123 | // round 4 124 | a = b + rol32((c ^ (b | ~d)) + a + x0 + 0xf4292244, 6); 125 | d = a + rol32((b ^ (a | ~c)) + d + x7 + 0x432aff97, 10); 126 | c = d + rol32((a ^ (d | ~b)) + c + xe + 0xab9423a7, 15); 127 | b = c + rol32((d ^ (c | ~a)) + b + x5 + 0xfc93a039, 21); 128 | a = b + rol32((c ^ (b | ~d)) + a + xc + 0x655b59c3, 6); 129 | d = a + rol32((b ^ (a | ~c)) + d + x3 + 0x8f0ccc92, 10); 130 | c = d + rol32((a ^ (d | ~b)) + c + xa + 0xffeff47d, 15); 131 | b = c + rol32((d ^ (c | ~a)) + b + x1 + 0x85845dd1, 21); 132 | a = b + rol32((c ^ (b | ~d)) + a + x8 + 0x6fa87e4f, 6); 133 | d = a + rol32((b ^ (a | ~c)) + d + xf + 0xfe2ce6e0, 10); 134 | c = d + rol32((a ^ (d | ~b)) + c + x6 + 0xa3014314, 15); 135 | b = c + rol32((d ^ (c | ~a)) + b + xd + 0x4e0811a1, 21); 136 | a = b + rol32((c ^ (b | ~d)) + a + x4 + 0xf7537e82, 6); 137 | d = a + rol32((b ^ (a | ~c)) + d + xb + 0xbd3af235, 10); 138 | c = d + rol32((a ^ (d | ~b)) + c + x2 + 0x2ad7d2bb, 15); 139 | b = c + rol32((d ^ (c | ~a)) + b + x9 + 0xeb86d391, 21); 140 | 141 | this.#a = (this.#a + a) >>> 0; 142 | this.#b = (this.#b + b) >>> 0; 143 | this.#c = (this.#c + c) >>> 0; 144 | this.#d = (this.#d + d) >>> 0; 145 | } 146 | 147 | /** 148 | * Update internal state 149 | * @param data data to update, data cannot exceed 2^32 bytes 150 | */ 151 | update(data: Message): this { 152 | let msg: Uint8Array; 153 | 154 | if (typeof data === "string") { 155 | msg = new TextEncoder().encode(data as string); 156 | } else if (typeof data === "object") { 157 | if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { 158 | msg = new Uint8Array(data); 159 | } else { 160 | throw new Error(TYPE_ERROR_MSG); 161 | } 162 | } else { 163 | throw new Error(TYPE_ERROR_MSG); 164 | } 165 | 166 | let pos = this.#pos; 167 | const free = BLOCK_SIZE - pos; 168 | 169 | if (msg.length < free) { 170 | this.#block.set(msg, pos); 171 | pos += msg.length; 172 | } else { 173 | // hash first block 174 | this.#block.set(msg.slice(0, free), pos); 175 | this.hash(this.#block); 176 | 177 | // hash as many blocks as possible 178 | let i = free; 179 | while (i + BLOCK_SIZE <= msg.length) { 180 | this.hash(msg.slice(i, i + BLOCK_SIZE)); 181 | i += BLOCK_SIZE; 182 | } 183 | 184 | // store leftover 185 | this.#block.fill(0).set(msg.slice(i), 0); 186 | pos = msg.length - i; 187 | } 188 | 189 | this.#pos = pos; 190 | this.addLength(msg.length); 191 | 192 | return this; 193 | } 194 | 195 | /** Returns final hash */ 196 | digest(): ArrayBuffer { 197 | let padLen = BLOCK_SIZE - this.#pos; 198 | if (padLen < 9) padLen += BLOCK_SIZE; 199 | 200 | const pad = new Uint8Array(padLen); 201 | 202 | pad[0] = 0x80; 203 | 204 | const n0 = this.#n0 << 3; 205 | const n1 = (this.#n1 << 3) | (this.#n0 >>> 29); 206 | pad[pad.length - 8] = n0 & 0xff; 207 | pad[pad.length - 7] = (n0 >>> 8) & 0xff; 208 | pad[pad.length - 6] = (n0 >>> 16) & 0xff; 209 | pad[pad.length - 5] = (n0 >>> 24) & 0xff; 210 | pad[pad.length - 4] = n1 & 0xff; 211 | pad[pad.length - 3] = (n1 >>> 8) & 0xff; 212 | pad[pad.length - 2] = (n1 >>> 16) & 0xff; 213 | pad[pad.length - 1] = (n1 >>> 24) & 0xff; 214 | 215 | this.update(pad.buffer); 216 | 217 | const hash = new ArrayBuffer(16); 218 | const hashView = new DataView(hash); 219 | hashView.setUint32(0, this.#a, true); 220 | hashView.setUint32(4, this.#b, true); 221 | hashView.setUint32(8, this.#c, true); 222 | hashView.setUint32(12, this.#d, true); 223 | 224 | return hash; 225 | } 226 | 227 | /** 228 | * Returns hash as a string of given format 229 | * @param format format of output string (hex or base64). Default is hex 230 | */ 231 | toString(format: "hex" | "base64" = "hex"): string { 232 | const hash = this.digest(); 233 | 234 | switch (format) { 235 | case "hex": 236 | return hex.encodeToString(new Uint8Array(hash)); 237 | case "base64": { 238 | const data = new Uint8Array(hash); 239 | let dataString = ""; 240 | for (let i = 0; i < data.length; ++i) { 241 | dataString += String.fromCharCode(data[i]); 242 | } 243 | return btoa(dataString); 244 | } 245 | default: 246 | throw new Error("md5: invalid format"); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /std/hash/sha1.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /* 3 | * [js-sha1]{@link https://github.com/emn178/js-sha1} 4 | * 5 | * @version 0.6.0 6 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 7 | * @copyright Chen, Yi-Cyuan 2014-2017 8 | * @license MIT 9 | */ 10 | 11 | export type Message = string | number[] | ArrayBuffer; 12 | 13 | const HEX_CHARS = "0123456789abcdef".split(""); 14 | const EXTRA = [-2147483648, 8388608, 32768, 128] as const; 15 | const SHIFT = [24, 16, 8, 0] as const; 16 | 17 | const blocks: number[] = []; 18 | 19 | export class Sha1 { 20 | #blocks!: number[]; 21 | #block!: number; 22 | #start!: number; 23 | #bytes!: number; 24 | #hBytes!: number; 25 | #finalized!: boolean; 26 | #hashed!: boolean; 27 | 28 | #h0 = 0x67452301; 29 | #h1 = 0xefcdab89; 30 | #h2 = 0x98badcfe; 31 | #h3 = 0x10325476; 32 | #h4 = 0xc3d2e1f0; 33 | #lastByteIndex = 0; 34 | 35 | constructor(sharedMemory = false) { 36 | if (sharedMemory) { 37 | // deno-fmt-ignore 38 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 39 | this.#blocks = blocks; 40 | } else { 41 | this.#blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 42 | } 43 | 44 | this.#h0 = 0x67452301; 45 | this.#h1 = 0xefcdab89; 46 | this.#h2 = 0x98badcfe; 47 | this.#h3 = 0x10325476; 48 | this.#h4 = 0xc3d2e1f0; 49 | 50 | this.#block = this.#start = this.#bytes = this.#hBytes = 0; 51 | this.#finalized = this.#hashed = false; 52 | } 53 | 54 | update(message: Message): this { 55 | if (this.#finalized) { 56 | return this; 57 | } 58 | 59 | let msg: string | number[] | Uint8Array | undefined; 60 | if (message instanceof ArrayBuffer) { 61 | msg = new Uint8Array(message); 62 | } else { 63 | msg = message; 64 | } 65 | 66 | let index = 0; 67 | const length = msg.length; 68 | const blocks = this.#blocks; 69 | 70 | while (index < length) { 71 | let i: number; 72 | if (this.#hashed) { 73 | this.#hashed = false; 74 | blocks[0] = this.#block; 75 | // deno-fmt-ignore 76 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 77 | } 78 | 79 | if (typeof msg !== "string") { 80 | for (i = this.#start; index < length && i < 64; ++index) { 81 | blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3]; 82 | } 83 | } else { 84 | for (i = this.#start; index < length && i < 64; ++index) { 85 | let code = msg.charCodeAt(index); 86 | if (code < 0x80) { 87 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 88 | } else if (code < 0x800) { 89 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 90 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 91 | } else if (code < 0xd800 || code >= 0xe000) { 92 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 93 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 94 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 95 | } else { 96 | code = 97 | 0x10000 + 98 | (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff)); 99 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 100 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 101 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 102 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 103 | } 104 | } 105 | } 106 | 107 | this.#lastByteIndex = i; 108 | this.#bytes += i - this.#start; 109 | if (i >= 64) { 110 | this.#block = blocks[16]; 111 | this.#start = i - 64; 112 | this.hash(); 113 | this.#hashed = true; 114 | } else { 115 | this.#start = i; 116 | } 117 | } 118 | if (this.#bytes > 4294967295) { 119 | this.#hBytes += (this.#bytes / 4294967296) >>> 0; 120 | this.#bytes = this.#bytes >>> 0; 121 | } 122 | return this; 123 | } 124 | 125 | private finalize(): void { 126 | if (this.#finalized) { 127 | return; 128 | } 129 | this.#finalized = true; 130 | const blocks = this.#blocks; 131 | const i = this.#lastByteIndex; 132 | blocks[16] = this.#block; 133 | blocks[i >> 2] |= EXTRA[i & 3]; 134 | this.#block = blocks[16]; 135 | if (i >= 56) { 136 | if (!this.#hashed) { 137 | this.hash(); 138 | } 139 | blocks[0] = this.#block; 140 | // deno-fmt-ignore 141 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 142 | } 143 | blocks[14] = (this.#hBytes << 3) | (this.#bytes >>> 29); 144 | blocks[15] = this.#bytes << 3; 145 | this.hash(); 146 | } 147 | 148 | private hash(): void { 149 | let a = this.#h0; 150 | let b = this.#h1; 151 | let c = this.#h2; 152 | let d = this.#h3; 153 | let e = this.#h4; 154 | let f: number; 155 | let j: number; 156 | let t: number; 157 | const blocks = this.#blocks; 158 | 159 | for (j = 16; j < 80; ++j) { 160 | t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16]; 161 | blocks[j] = (t << 1) | (t >>> 31); 162 | } 163 | 164 | for (j = 0; j < 20; j += 5) { 165 | f = (b & c) | (~b & d); 166 | t = (a << 5) | (a >>> 27); 167 | e = (t + f + e + 1518500249 + blocks[j]) >>> 0; 168 | b = (b << 30) | (b >>> 2); 169 | 170 | f = (a & b) | (~a & c); 171 | t = (e << 5) | (e >>> 27); 172 | d = (t + f + d + 1518500249 + blocks[j + 1]) >>> 0; 173 | a = (a << 30) | (a >>> 2); 174 | 175 | f = (e & a) | (~e & b); 176 | t = (d << 5) | (d >>> 27); 177 | c = (t + f + c + 1518500249 + blocks[j + 2]) >>> 0; 178 | e = (e << 30) | (e >>> 2); 179 | 180 | f = (d & e) | (~d & a); 181 | t = (c << 5) | (c >>> 27); 182 | b = (t + f + b + 1518500249 + blocks[j + 3]) >>> 0; 183 | d = (d << 30) | (d >>> 2); 184 | 185 | f = (c & d) | (~c & e); 186 | t = (b << 5) | (b >>> 27); 187 | a = (t + f + a + 1518500249 + blocks[j + 4]) >>> 0; 188 | c = (c << 30) | (c >>> 2); 189 | } 190 | 191 | for (; j < 40; j += 5) { 192 | f = b ^ c ^ d; 193 | t = (a << 5) | (a >>> 27); 194 | e = (t + f + e + 1859775393 + blocks[j]) >>> 0; 195 | b = (b << 30) | (b >>> 2); 196 | 197 | f = a ^ b ^ c; 198 | t = (e << 5) | (e >>> 27); 199 | d = (t + f + d + 1859775393 + blocks[j + 1]) >>> 0; 200 | a = (a << 30) | (a >>> 2); 201 | 202 | f = e ^ a ^ b; 203 | t = (d << 5) | (d >>> 27); 204 | c = (t + f + c + 1859775393 + blocks[j + 2]) >>> 0; 205 | e = (e << 30) | (e >>> 2); 206 | 207 | f = d ^ e ^ a; 208 | t = (c << 5) | (c >>> 27); 209 | b = (t + f + b + 1859775393 + blocks[j + 3]) >>> 0; 210 | d = (d << 30) | (d >>> 2); 211 | 212 | f = c ^ d ^ e; 213 | t = (b << 5) | (b >>> 27); 214 | a = (t + f + a + 1859775393 + blocks[j + 4]) >>> 0; 215 | c = (c << 30) | (c >>> 2); 216 | } 217 | 218 | for (; j < 60; j += 5) { 219 | f = (b & c) | (b & d) | (c & d); 220 | t = (a << 5) | (a >>> 27); 221 | e = (t + f + e - 1894007588 + blocks[j]) >>> 0; 222 | b = (b << 30) | (b >>> 2); 223 | 224 | f = (a & b) | (a & c) | (b & c); 225 | t = (e << 5) | (e >>> 27); 226 | d = (t + f + d - 1894007588 + blocks[j + 1]) >>> 0; 227 | a = (a << 30) | (a >>> 2); 228 | 229 | f = (e & a) | (e & b) | (a & b); 230 | t = (d << 5) | (d >>> 27); 231 | c = (t + f + c - 1894007588 + blocks[j + 2]) >>> 0; 232 | e = (e << 30) | (e >>> 2); 233 | 234 | f = (d & e) | (d & a) | (e & a); 235 | t = (c << 5) | (c >>> 27); 236 | b = (t + f + b - 1894007588 + blocks[j + 3]) >>> 0; 237 | d = (d << 30) | (d >>> 2); 238 | 239 | f = (c & d) | (c & e) | (d & e); 240 | t = (b << 5) | (b >>> 27); 241 | a = (t + f + a - 1894007588 + blocks[j + 4]) >>> 0; 242 | c = (c << 30) | (c >>> 2); 243 | } 244 | 245 | for (; j < 80; j += 5) { 246 | f = b ^ c ^ d; 247 | t = (a << 5) | (a >>> 27); 248 | e = (t + f + e - 899497514 + blocks[j]) >>> 0; 249 | b = (b << 30) | (b >>> 2); 250 | 251 | f = a ^ b ^ c; 252 | t = (e << 5) | (e >>> 27); 253 | d = (t + f + d - 899497514 + blocks[j + 1]) >>> 0; 254 | a = (a << 30) | (a >>> 2); 255 | 256 | f = e ^ a ^ b; 257 | t = (d << 5) | (d >>> 27); 258 | c = (t + f + c - 899497514 + blocks[j + 2]) >>> 0; 259 | e = (e << 30) | (e >>> 2); 260 | 261 | f = d ^ e ^ a; 262 | t = (c << 5) | (c >>> 27); 263 | b = (t + f + b - 899497514 + blocks[j + 3]) >>> 0; 264 | d = (d << 30) | (d >>> 2); 265 | 266 | f = c ^ d ^ e; 267 | t = (b << 5) | (b >>> 27); 268 | a = (t + f + a - 899497514 + blocks[j + 4]) >>> 0; 269 | c = (c << 30) | (c >>> 2); 270 | } 271 | 272 | this.#h0 = (this.#h0 + a) >>> 0; 273 | this.#h1 = (this.#h1 + b) >>> 0; 274 | this.#h2 = (this.#h2 + c) >>> 0; 275 | this.#h3 = (this.#h3 + d) >>> 0; 276 | this.#h4 = (this.#h4 + e) >>> 0; 277 | } 278 | 279 | hex(): string { 280 | this.finalize(); 281 | 282 | const h0 = this.#h0; 283 | const h1 = this.#h1; 284 | const h2 = this.#h2; 285 | const h3 = this.#h3; 286 | const h4 = this.#h4; 287 | 288 | return ( 289 | HEX_CHARS[(h0 >> 28) & 0x0f] + 290 | HEX_CHARS[(h0 >> 24) & 0x0f] + 291 | HEX_CHARS[(h0 >> 20) & 0x0f] + 292 | HEX_CHARS[(h0 >> 16) & 0x0f] + 293 | HEX_CHARS[(h0 >> 12) & 0x0f] + 294 | HEX_CHARS[(h0 >> 8) & 0x0f] + 295 | HEX_CHARS[(h0 >> 4) & 0x0f] + 296 | HEX_CHARS[h0 & 0x0f] + 297 | HEX_CHARS[(h1 >> 28) & 0x0f] + 298 | HEX_CHARS[(h1 >> 24) & 0x0f] + 299 | HEX_CHARS[(h1 >> 20) & 0x0f] + 300 | HEX_CHARS[(h1 >> 16) & 0x0f] + 301 | HEX_CHARS[(h1 >> 12) & 0x0f] + 302 | HEX_CHARS[(h1 >> 8) & 0x0f] + 303 | HEX_CHARS[(h1 >> 4) & 0x0f] + 304 | HEX_CHARS[h1 & 0x0f] + 305 | HEX_CHARS[(h2 >> 28) & 0x0f] + 306 | HEX_CHARS[(h2 >> 24) & 0x0f] + 307 | HEX_CHARS[(h2 >> 20) & 0x0f] + 308 | HEX_CHARS[(h2 >> 16) & 0x0f] + 309 | HEX_CHARS[(h2 >> 12) & 0x0f] + 310 | HEX_CHARS[(h2 >> 8) & 0x0f] + 311 | HEX_CHARS[(h2 >> 4) & 0x0f] + 312 | HEX_CHARS[h2 & 0x0f] + 313 | HEX_CHARS[(h3 >> 28) & 0x0f] + 314 | HEX_CHARS[(h3 >> 24) & 0x0f] + 315 | HEX_CHARS[(h3 >> 20) & 0x0f] + 316 | HEX_CHARS[(h3 >> 16) & 0x0f] + 317 | HEX_CHARS[(h3 >> 12) & 0x0f] + 318 | HEX_CHARS[(h3 >> 8) & 0x0f] + 319 | HEX_CHARS[(h3 >> 4) & 0x0f] + 320 | HEX_CHARS[h3 & 0x0f] + 321 | HEX_CHARS[(h4 >> 28) & 0x0f] + 322 | HEX_CHARS[(h4 >> 24) & 0x0f] + 323 | HEX_CHARS[(h4 >> 20) & 0x0f] + 324 | HEX_CHARS[(h4 >> 16) & 0x0f] + 325 | HEX_CHARS[(h4 >> 12) & 0x0f] + 326 | HEX_CHARS[(h4 >> 8) & 0x0f] + 327 | HEX_CHARS[(h4 >> 4) & 0x0f] + 328 | HEX_CHARS[h4 & 0x0f] 329 | ); 330 | } 331 | 332 | toString(): string { 333 | return this.hex(); 334 | } 335 | 336 | digest(): number[] { 337 | this.finalize(); 338 | 339 | const h0 = this.#h0; 340 | const h1 = this.#h1; 341 | const h2 = this.#h2; 342 | const h3 = this.#h3; 343 | const h4 = this.#h4; 344 | 345 | return [ 346 | (h0 >> 24) & 0xff, 347 | (h0 >> 16) & 0xff, 348 | (h0 >> 8) & 0xff, 349 | h0 & 0xff, 350 | (h1 >> 24) & 0xff, 351 | (h1 >> 16) & 0xff, 352 | (h1 >> 8) & 0xff, 353 | h1 & 0xff, 354 | (h2 >> 24) & 0xff, 355 | (h2 >> 16) & 0xff, 356 | (h2 >> 8) & 0xff, 357 | h2 & 0xff, 358 | (h3 >> 24) & 0xff, 359 | (h3 >> 16) & 0xff, 360 | (h3 >> 8) & 0xff, 361 | h3 & 0xff, 362 | (h4 >> 24) & 0xff, 363 | (h4 >> 16) & 0xff, 364 | (h4 >> 8) & 0xff, 365 | h4 & 0xff, 366 | ]; 367 | } 368 | 369 | array(): number[] { 370 | return this.digest(); 371 | } 372 | 373 | arrayBuffer(): ArrayBuffer { 374 | this.finalize(); 375 | 376 | const buffer = new ArrayBuffer(20); 377 | const dataView = new DataView(buffer); 378 | dataView.setUint32(0, this.#h0); 379 | dataView.setUint32(4, this.#h1); 380 | dataView.setUint32(8, this.#h2); 381 | dataView.setUint32(12, this.#h3); 382 | dataView.setUint32(16, this.#h4); 383 | 384 | return buffer; 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /std/hash/sha256.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /* 3 | * Adapted to deno from: 4 | * 5 | * [js-sha256]{@link https://github.com/emn178/js-sha256} 6 | * 7 | * @version 0.9.0 8 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 9 | * @copyright Chen, Yi-Cyuan 2014-2017 10 | * @license MIT 11 | */ 12 | 13 | export type Message = string | number[] | ArrayBuffer; 14 | 15 | const HEX_CHARS = "0123456789abcdef".split(""); 16 | const EXTRA = [-2147483648, 8388608, 32768, 128] as const; 17 | const SHIFT = [24, 16, 8, 0] as const; 18 | // deno-fmt-ignore 19 | const K = [ 20 | 0x428a2f98, 21 | 0x71374491, 22 | 0xb5c0fbcf, 23 | 0xe9b5dba5, 24 | 0x3956c25b, 25 | 0x59f111f1, 26 | 0x923f82a4, 27 | 0xab1c5ed5, 28 | 0xd807aa98, 29 | 0x12835b01, 30 | 0x243185be, 31 | 0x550c7dc3, 32 | 0x72be5d74, 33 | 0x80deb1fe, 34 | 0x9bdc06a7, 35 | 0xc19bf174, 36 | 0xe49b69c1, 37 | 0xefbe4786, 38 | 0x0fc19dc6, 39 | 0x240ca1cc, 40 | 0x2de92c6f, 41 | 0x4a7484aa, 42 | 0x5cb0a9dc, 43 | 0x76f988da, 44 | 0x983e5152, 45 | 0xa831c66d, 46 | 0xb00327c8, 47 | 0xbf597fc7, 48 | 0xc6e00bf3, 49 | 0xd5a79147, 50 | 0x06ca6351, 51 | 0x14292967, 52 | 0x27b70a85, 53 | 0x2e1b2138, 54 | 0x4d2c6dfc, 55 | 0x53380d13, 56 | 0x650a7354, 57 | 0x766a0abb, 58 | 0x81c2c92e, 59 | 0x92722c85, 60 | 0xa2bfe8a1, 61 | 0xa81a664b, 62 | 0xc24b8b70, 63 | 0xc76c51a3, 64 | 0xd192e819, 65 | 0xd6990624, 66 | 0xf40e3585, 67 | 0x106aa070, 68 | 0x19a4c116, 69 | 0x1e376c08, 70 | 0x2748774c, 71 | 0x34b0bcb5, 72 | 0x391c0cb3, 73 | 0x4ed8aa4a, 74 | 0x5b9cca4f, 75 | 0x682e6ff3, 76 | 0x748f82ee, 77 | 0x78a5636f, 78 | 0x84c87814, 79 | 0x8cc70208, 80 | 0x90befffa, 81 | 0xa4506ceb, 82 | 0xbef9a3f7, 83 | 0xc67178f2, 84 | ] as const; 85 | 86 | const blocks: number[] = []; 87 | 88 | export class Sha256 { 89 | #block!: number; 90 | #blocks!: number[]; 91 | #bytes!: number; 92 | #finalized!: boolean; 93 | #first!: boolean; 94 | #h0!: number; 95 | #h1!: number; 96 | #h2!: number; 97 | #h3!: number; 98 | #h4!: number; 99 | #h5!: number; 100 | #h6!: number; 101 | #h7!: number; 102 | #hashed!: boolean; 103 | #hBytes!: number; 104 | #is224!: boolean; 105 | #lastByteIndex = 0; 106 | #start!: number; 107 | 108 | constructor(is224 = false, sharedMemory = false) { 109 | this.init(is224, sharedMemory); 110 | } 111 | 112 | protected init(is224: boolean, sharedMemory: boolean): void { 113 | if (sharedMemory) { 114 | // deno-fmt-ignore 115 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 116 | this.#blocks = blocks; 117 | } else { 118 | this.#blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 119 | } 120 | 121 | if (is224) { 122 | this.#h0 = 0xc1059ed8; 123 | this.#h1 = 0x367cd507; 124 | this.#h2 = 0x3070dd17; 125 | this.#h3 = 0xf70e5939; 126 | this.#h4 = 0xffc00b31; 127 | this.#h5 = 0x68581511; 128 | this.#h6 = 0x64f98fa7; 129 | this.#h7 = 0xbefa4fa4; 130 | } else { 131 | // 256 132 | this.#h0 = 0x6a09e667; 133 | this.#h1 = 0xbb67ae85; 134 | this.#h2 = 0x3c6ef372; 135 | this.#h3 = 0xa54ff53a; 136 | this.#h4 = 0x510e527f; 137 | this.#h5 = 0x9b05688c; 138 | this.#h6 = 0x1f83d9ab; 139 | this.#h7 = 0x5be0cd19; 140 | } 141 | 142 | this.#block = this.#start = this.#bytes = this.#hBytes = 0; 143 | this.#finalized = this.#hashed = false; 144 | this.#first = true; 145 | this.#is224 = is224; 146 | } 147 | 148 | /** Update hash 149 | * 150 | * @param message The message you want to hash. 151 | */ 152 | update(message: Message): this { 153 | if (this.#finalized) { 154 | return this; 155 | } 156 | 157 | let msg: string | number[] | Uint8Array | undefined; 158 | if (message instanceof ArrayBuffer) { 159 | msg = new Uint8Array(message); 160 | } else { 161 | msg = message; 162 | } 163 | 164 | let index = 0; 165 | const length = msg.length; 166 | const blocks = this.#blocks; 167 | 168 | while (index < length) { 169 | let i: number; 170 | if (this.#hashed) { 171 | this.#hashed = false; 172 | blocks[0] = this.#block; 173 | // deno-fmt-ignore 174 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 175 | } 176 | 177 | if (typeof msg !== "string") { 178 | for (i = this.#start; index < length && i < 64; ++index) { 179 | blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3]; 180 | } 181 | } else { 182 | for (i = this.#start; index < length && i < 64; ++index) { 183 | let code = msg.charCodeAt(index); 184 | if (code < 0x80) { 185 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 186 | } else if (code < 0x800) { 187 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 188 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 189 | } else if (code < 0xd800 || code >= 0xe000) { 190 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 191 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 192 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 193 | } else { 194 | code = 195 | 0x10000 + 196 | (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff)); 197 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 198 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 199 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 200 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 201 | } 202 | } 203 | } 204 | 205 | this.#lastByteIndex = i; 206 | this.#bytes += i - this.#start; 207 | if (i >= 64) { 208 | this.#block = blocks[16]; 209 | this.#start = i - 64; 210 | this.hash(); 211 | this.#hashed = true; 212 | } else { 213 | this.#start = i; 214 | } 215 | } 216 | if (this.#bytes > 4294967295) { 217 | this.#hBytes += (this.#bytes / 4294967296) << 0; 218 | this.#bytes = this.#bytes % 4294967296; 219 | } 220 | return this; 221 | } 222 | 223 | protected finalize(): void { 224 | if (this.#finalized) { 225 | return; 226 | } 227 | this.#finalized = true; 228 | const blocks = this.#blocks; 229 | const i = this.#lastByteIndex; 230 | blocks[16] = this.#block; 231 | blocks[i >> 2] |= EXTRA[i & 3]; 232 | this.#block = blocks[16]; 233 | if (i >= 56) { 234 | if (!this.#hashed) { 235 | this.hash(); 236 | } 237 | blocks[0] = this.#block; 238 | // deno-fmt-ignore 239 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 240 | } 241 | blocks[14] = (this.#hBytes << 3) | (this.#bytes >>> 29); 242 | blocks[15] = this.#bytes << 3; 243 | this.hash(); 244 | } 245 | 246 | protected hash(): void { 247 | let a = this.#h0; 248 | let b = this.#h1; 249 | let c = this.#h2; 250 | let d = this.#h3; 251 | let e = this.#h4; 252 | let f = this.#h5; 253 | let g = this.#h6; 254 | let h = this.#h7; 255 | const blocks = this.#blocks; 256 | let s0: number; 257 | let s1: number; 258 | let maj: number; 259 | let t1: number; 260 | let t2: number; 261 | let ch: number; 262 | let ab: number; 263 | let da: number; 264 | let cd: number; 265 | let bc: number; 266 | 267 | for (let j = 16; j < 64; ++j) { 268 | // rightrotate 269 | t1 = blocks[j - 15]; 270 | s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); 271 | t1 = blocks[j - 2]; 272 | s1 = 273 | ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); 274 | blocks[j] = (blocks[j - 16] + s0 + blocks[j - 7] + s1) << 0; 275 | } 276 | 277 | bc = b & c; 278 | for (let j = 0; j < 64; j += 4) { 279 | if (this.#first) { 280 | if (this.#is224) { 281 | ab = 300032; 282 | t1 = blocks[0] - 1413257819; 283 | h = (t1 - 150054599) << 0; 284 | d = (t1 + 24177077) << 0; 285 | } else { 286 | ab = 704751109; 287 | t1 = blocks[0] - 210244248; 288 | h = (t1 - 1521486534) << 0; 289 | d = (t1 + 143694565) << 0; 290 | } 291 | this.#first = false; 292 | } else { 293 | s0 = 294 | ((a >>> 2) | (a << 30)) ^ 295 | ((a >>> 13) | (a << 19)) ^ 296 | ((a >>> 22) | (a << 10)); 297 | s1 = 298 | ((e >>> 6) | (e << 26)) ^ 299 | ((e >>> 11) | (e << 21)) ^ 300 | ((e >>> 25) | (e << 7)); 301 | ab = a & b; 302 | maj = ab ^ (a & c) ^ bc; 303 | ch = (e & f) ^ (~e & g); 304 | t1 = h + s1 + ch + K[j] + blocks[j]; 305 | t2 = s0 + maj; 306 | h = (d + t1) << 0; 307 | d = (t1 + t2) << 0; 308 | } 309 | s0 = 310 | ((d >>> 2) | (d << 30)) ^ 311 | ((d >>> 13) | (d << 19)) ^ 312 | ((d >>> 22) | (d << 10)); 313 | s1 = 314 | ((h >>> 6) | (h << 26)) ^ 315 | ((h >>> 11) | (h << 21)) ^ 316 | ((h >>> 25) | (h << 7)); 317 | da = d & a; 318 | maj = da ^ (d & b) ^ ab; 319 | ch = (h & e) ^ (~h & f); 320 | t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; 321 | t2 = s0 + maj; 322 | g = (c + t1) << 0; 323 | c = (t1 + t2) << 0; 324 | s0 = 325 | ((c >>> 2) | (c << 30)) ^ 326 | ((c >>> 13) | (c << 19)) ^ 327 | ((c >>> 22) | (c << 10)); 328 | s1 = 329 | ((g >>> 6) | (g << 26)) ^ 330 | ((g >>> 11) | (g << 21)) ^ 331 | ((g >>> 25) | (g << 7)); 332 | cd = c & d; 333 | maj = cd ^ (c & a) ^ da; 334 | ch = (g & h) ^ (~g & e); 335 | t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; 336 | t2 = s0 + maj; 337 | f = (b + t1) << 0; 338 | b = (t1 + t2) << 0; 339 | s0 = 340 | ((b >>> 2) | (b << 30)) ^ 341 | ((b >>> 13) | (b << 19)) ^ 342 | ((b >>> 22) | (b << 10)); 343 | s1 = 344 | ((f >>> 6) | (f << 26)) ^ 345 | ((f >>> 11) | (f << 21)) ^ 346 | ((f >>> 25) | (f << 7)); 347 | bc = b & c; 348 | maj = bc ^ (b & d) ^ cd; 349 | ch = (f & g) ^ (~f & h); 350 | t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; 351 | t2 = s0 + maj; 352 | e = (a + t1) << 0; 353 | a = (t1 + t2) << 0; 354 | } 355 | 356 | this.#h0 = (this.#h0 + a) << 0; 357 | this.#h1 = (this.#h1 + b) << 0; 358 | this.#h2 = (this.#h2 + c) << 0; 359 | this.#h3 = (this.#h3 + d) << 0; 360 | this.#h4 = (this.#h4 + e) << 0; 361 | this.#h5 = (this.#h5 + f) << 0; 362 | this.#h6 = (this.#h6 + g) << 0; 363 | this.#h7 = (this.#h7 + h) << 0; 364 | } 365 | 366 | /** Return hash in hex string. */ 367 | hex(): string { 368 | this.finalize(); 369 | 370 | const h0 = this.#h0; 371 | const h1 = this.#h1; 372 | const h2 = this.#h2; 373 | const h3 = this.#h3; 374 | const h4 = this.#h4; 375 | const h5 = this.#h5; 376 | const h6 = this.#h6; 377 | const h7 = this.#h7; 378 | 379 | let hex = 380 | HEX_CHARS[(h0 >> 28) & 0x0f] + 381 | HEX_CHARS[(h0 >> 24) & 0x0f] + 382 | HEX_CHARS[(h0 >> 20) & 0x0f] + 383 | HEX_CHARS[(h0 >> 16) & 0x0f] + 384 | HEX_CHARS[(h0 >> 12) & 0x0f] + 385 | HEX_CHARS[(h0 >> 8) & 0x0f] + 386 | HEX_CHARS[(h0 >> 4) & 0x0f] + 387 | HEX_CHARS[h0 & 0x0f] + 388 | HEX_CHARS[(h1 >> 28) & 0x0f] + 389 | HEX_CHARS[(h1 >> 24) & 0x0f] + 390 | HEX_CHARS[(h1 >> 20) & 0x0f] + 391 | HEX_CHARS[(h1 >> 16) & 0x0f] + 392 | HEX_CHARS[(h1 >> 12) & 0x0f] + 393 | HEX_CHARS[(h1 >> 8) & 0x0f] + 394 | HEX_CHARS[(h1 >> 4) & 0x0f] + 395 | HEX_CHARS[h1 & 0x0f] + 396 | HEX_CHARS[(h2 >> 28) & 0x0f] + 397 | HEX_CHARS[(h2 >> 24) & 0x0f] + 398 | HEX_CHARS[(h2 >> 20) & 0x0f] + 399 | HEX_CHARS[(h2 >> 16) & 0x0f] + 400 | HEX_CHARS[(h2 >> 12) & 0x0f] + 401 | HEX_CHARS[(h2 >> 8) & 0x0f] + 402 | HEX_CHARS[(h2 >> 4) & 0x0f] + 403 | HEX_CHARS[h2 & 0x0f] + 404 | HEX_CHARS[(h3 >> 28) & 0x0f] + 405 | HEX_CHARS[(h3 >> 24) & 0x0f] + 406 | HEX_CHARS[(h3 >> 20) & 0x0f] + 407 | HEX_CHARS[(h3 >> 16) & 0x0f] + 408 | HEX_CHARS[(h3 >> 12) & 0x0f] + 409 | HEX_CHARS[(h3 >> 8) & 0x0f] + 410 | HEX_CHARS[(h3 >> 4) & 0x0f] + 411 | HEX_CHARS[h3 & 0x0f] + 412 | HEX_CHARS[(h4 >> 28) & 0x0f] + 413 | HEX_CHARS[(h4 >> 24) & 0x0f] + 414 | HEX_CHARS[(h4 >> 20) & 0x0f] + 415 | HEX_CHARS[(h4 >> 16) & 0x0f] + 416 | HEX_CHARS[(h4 >> 12) & 0x0f] + 417 | HEX_CHARS[(h4 >> 8) & 0x0f] + 418 | HEX_CHARS[(h4 >> 4) & 0x0f] + 419 | HEX_CHARS[h4 & 0x0f] + 420 | HEX_CHARS[(h5 >> 28) & 0x0f] + 421 | HEX_CHARS[(h5 >> 24) & 0x0f] + 422 | HEX_CHARS[(h5 >> 20) & 0x0f] + 423 | HEX_CHARS[(h5 >> 16) & 0x0f] + 424 | HEX_CHARS[(h5 >> 12) & 0x0f] + 425 | HEX_CHARS[(h5 >> 8) & 0x0f] + 426 | HEX_CHARS[(h5 >> 4) & 0x0f] + 427 | HEX_CHARS[h5 & 0x0f] + 428 | HEX_CHARS[(h6 >> 28) & 0x0f] + 429 | HEX_CHARS[(h6 >> 24) & 0x0f] + 430 | HEX_CHARS[(h6 >> 20) & 0x0f] + 431 | HEX_CHARS[(h6 >> 16) & 0x0f] + 432 | HEX_CHARS[(h6 >> 12) & 0x0f] + 433 | HEX_CHARS[(h6 >> 8) & 0x0f] + 434 | HEX_CHARS[(h6 >> 4) & 0x0f] + 435 | HEX_CHARS[h6 & 0x0f]; 436 | if (!this.#is224) { 437 | hex += 438 | HEX_CHARS[(h7 >> 28) & 0x0f] + 439 | HEX_CHARS[(h7 >> 24) & 0x0f] + 440 | HEX_CHARS[(h7 >> 20) & 0x0f] + 441 | HEX_CHARS[(h7 >> 16) & 0x0f] + 442 | HEX_CHARS[(h7 >> 12) & 0x0f] + 443 | HEX_CHARS[(h7 >> 8) & 0x0f] + 444 | HEX_CHARS[(h7 >> 4) & 0x0f] + 445 | HEX_CHARS[h7 & 0x0f]; 446 | } 447 | return hex; 448 | } 449 | 450 | /** Return hash in hex string. */ 451 | toString(): string { 452 | return this.hex(); 453 | } 454 | 455 | /** Return hash in integer array. */ 456 | digest(): number[] { 457 | this.finalize(); 458 | 459 | const h0 = this.#h0; 460 | const h1 = this.#h1; 461 | const h2 = this.#h2; 462 | const h3 = this.#h3; 463 | const h4 = this.#h4; 464 | const h5 = this.#h5; 465 | const h6 = this.#h6; 466 | const h7 = this.#h7; 467 | 468 | const arr = [ 469 | (h0 >> 24) & 0xff, 470 | (h0 >> 16) & 0xff, 471 | (h0 >> 8) & 0xff, 472 | h0 & 0xff, 473 | (h1 >> 24) & 0xff, 474 | (h1 >> 16) & 0xff, 475 | (h1 >> 8) & 0xff, 476 | h1 & 0xff, 477 | (h2 >> 24) & 0xff, 478 | (h2 >> 16) & 0xff, 479 | (h2 >> 8) & 0xff, 480 | h2 & 0xff, 481 | (h3 >> 24) & 0xff, 482 | (h3 >> 16) & 0xff, 483 | (h3 >> 8) & 0xff, 484 | h3 & 0xff, 485 | (h4 >> 24) & 0xff, 486 | (h4 >> 16) & 0xff, 487 | (h4 >> 8) & 0xff, 488 | h4 & 0xff, 489 | (h5 >> 24) & 0xff, 490 | (h5 >> 16) & 0xff, 491 | (h5 >> 8) & 0xff, 492 | h5 & 0xff, 493 | (h6 >> 24) & 0xff, 494 | (h6 >> 16) & 0xff, 495 | (h6 >> 8) & 0xff, 496 | h6 & 0xff, 497 | ]; 498 | if (!this.#is224) { 499 | arr.push( 500 | (h7 >> 24) & 0xff, 501 | (h7 >> 16) & 0xff, 502 | (h7 >> 8) & 0xff, 503 | h7 & 0xff 504 | ); 505 | } 506 | return arr; 507 | } 508 | 509 | /** Return hash in integer array. */ 510 | array(): number[] { 511 | return this.digest(); 512 | } 513 | 514 | /** Return hash in ArrayBuffer. */ 515 | arrayBuffer(): ArrayBuffer { 516 | this.finalize(); 517 | 518 | const buffer = new ArrayBuffer(this.#is224 ? 28 : 32); 519 | const dataView = new DataView(buffer); 520 | dataView.setUint32(0, this.#h0); 521 | dataView.setUint32(4, this.#h1); 522 | dataView.setUint32(8, this.#h2); 523 | dataView.setUint32(12, this.#h3); 524 | dataView.setUint32(16, this.#h4); 525 | dataView.setUint32(20, this.#h5); 526 | dataView.setUint32(24, this.#h6); 527 | if (!this.#is224) { 528 | dataView.setUint32(28, this.#h7); 529 | } 530 | return buffer; 531 | } 532 | } 533 | 534 | export class HmacSha256 extends Sha256 { 535 | #inner: boolean; 536 | #is224: boolean; 537 | #oKeyPad: number[]; 538 | #sharedMemory: boolean; 539 | 540 | constructor(secretKey: Message, is224 = false, sharedMemory = false) { 541 | super(is224, sharedMemory); 542 | 543 | let key: number[] | Uint8Array | undefined; 544 | if (typeof secretKey === "string") { 545 | const bytes: number[] = []; 546 | const length = secretKey.length; 547 | let index = 0; 548 | for (let i = 0; i < length; ++i) { 549 | let code = secretKey.charCodeAt(i); 550 | if (code < 0x80) { 551 | bytes[index++] = code; 552 | } else if (code < 0x800) { 553 | bytes[index++] = 0xc0 | (code >> 6); 554 | bytes[index++] = 0x80 | (code & 0x3f); 555 | } else if (code < 0xd800 || code >= 0xe000) { 556 | bytes[index++] = 0xe0 | (code >> 12); 557 | bytes[index++] = 0x80 | ((code >> 6) & 0x3f); 558 | bytes[index++] = 0x80 | (code & 0x3f); 559 | } else { 560 | code = 561 | 0x10000 + 562 | (((code & 0x3ff) << 10) | (secretKey.charCodeAt(++i) & 0x3ff)); 563 | bytes[index++] = 0xf0 | (code >> 18); 564 | bytes[index++] = 0x80 | ((code >> 12) & 0x3f); 565 | bytes[index++] = 0x80 | ((code >> 6) & 0x3f); 566 | bytes[index++] = 0x80 | (code & 0x3f); 567 | } 568 | } 569 | key = bytes; 570 | } else { 571 | if (secretKey instanceof ArrayBuffer) { 572 | key = new Uint8Array(secretKey); 573 | } else { 574 | key = secretKey; 575 | } 576 | } 577 | 578 | if (key.length > 64) { 579 | key = new Sha256(is224, true).update(key).array(); 580 | } 581 | 582 | const oKeyPad: number[] = []; 583 | const iKeyPad: number[] = []; 584 | for (let i = 0; i < 64; ++i) { 585 | const b = key[i] || 0; 586 | oKeyPad[i] = 0x5c ^ b; 587 | iKeyPad[i] = 0x36 ^ b; 588 | } 589 | 590 | this.update(iKeyPad); 591 | this.#oKeyPad = oKeyPad; 592 | this.#inner = true; 593 | this.#is224 = is224; 594 | this.#sharedMemory = sharedMemory; 595 | } 596 | 597 | protected finalize(): void { 598 | super.finalize(); 599 | if (this.#inner) { 600 | this.#inner = false; 601 | const innerHash = this.array(); 602 | super.init(this.#is224, this.#sharedMemory); 603 | this.update(this.#oKeyPad); 604 | this.update(innerHash); 605 | super.finalize(); 606 | } 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /std/uuid/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | /** 3 | * Converts the byte array to a UUID string 4 | * @param bytes Used to convert Byte to Hex 5 | */ 6 | export function bytesToUuid(bytes: number[] | Uint8Array): string { 7 | const bits: string[] = [...bytes].map((bit): string => { 8 | const s: string = bit.toString(16); 9 | return bit < 0x10 ? "0" + s : s; 10 | }); 11 | return [ 12 | ...bits.slice(0, 4), 13 | "-", 14 | ...bits.slice(4, 6), 15 | "-", 16 | ...bits.slice(6, 8), 17 | "-", 18 | ...bits.slice(8, 10), 19 | "-", 20 | ...bits.slice(10, 16), 21 | ].join(""); 22 | } 23 | /** 24 | * Converts a string to a byte array by converting the hex value to a number 25 | * @param uuid Value that gets converted 26 | */ 27 | export function uuidToBytes(uuid: string): number[] { 28 | const bytes: number[] = []; 29 | 30 | uuid.replace(/[a-fA-F0-9]{2}/g, (hex: string): string => { 31 | bytes.push(parseInt(hex, 16)); 32 | return ""; 33 | }); 34 | 35 | return bytes; 36 | } 37 | /** 38 | * Converts a string to a byte array using the char code 39 | * @param str Value that gets converted 40 | */ 41 | export function stringToBytes(str: string): number[] { 42 | str = unescape(encodeURIComponent(str)); 43 | const bytes = new Array(str.length); 44 | for (let i = 0; i < str.length; i++) { 45 | bytes[i] = str.charCodeAt(i); 46 | } 47 | return bytes; 48 | } 49 | /** 50 | * Creates a buffer for creating a SHA-1 hash 51 | * @param content Buffer for SHA-1 hash 52 | */ 53 | export function createBuffer(content: number[]): ArrayBuffer { 54 | const arrayBuffer = new ArrayBuffer(content.length); 55 | const uint8Array = new Uint8Array(arrayBuffer); 56 | for (let i = 0; i < content.length; i++) { 57 | uint8Array[i] = content[i]; 58 | } 59 | return arrayBuffer; 60 | } 61 | -------------------------------------------------------------------------------- /std/uuid/v1.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | import { bytesToUuid } from "./common.ts"; 3 | 4 | const UUID_RE = new RegExp( 5 | "^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", 6 | "i" 7 | ); 8 | /** 9 | * Validates the UUID v1 10 | * @param id UUID value 11 | */ 12 | export function validate(id: string): boolean { 13 | return UUID_RE.test(id); 14 | } 15 | 16 | let _nodeId: number[]; 17 | let _clockseq: number; 18 | 19 | let _lastMSecs = 0; 20 | let _lastNSecs = 0; 21 | 22 | type V1Options = { 23 | node?: number[]; 24 | clockseq?: number; 25 | msecs?: number; 26 | nsecs?: number; 27 | random?: number[]; 28 | rng?: () => number[]; 29 | }; 30 | /** 31 | * Generates a RFC4122 v1 UUID (time-based) 32 | * @param options Can use RFC time sequence values as overwrites 33 | * @param buf Can allow the UUID to be written in byte-form starting at the offset 34 | * @param offset Index to start writing on the UUID bytes in buffer 35 | */ 36 | export function generate( 37 | options?: V1Options | null, 38 | buf?: number[], 39 | offset?: number 40 | ): string | number[] { 41 | let i = (buf && offset) || 0; 42 | const b = buf || []; 43 | 44 | options = options || {}; 45 | let node = options.node || _nodeId; 46 | let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; 47 | 48 | if (node == null || clockseq == null) { 49 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 50 | const seedBytes: any = 51 | options.random || 52 | options.rng || 53 | crypto.getRandomValues(new Uint8Array(16)); 54 | if (node == null) { 55 | node = _nodeId = [ 56 | seedBytes[0] | 0x01, 57 | seedBytes[1], 58 | seedBytes[2], 59 | seedBytes[3], 60 | seedBytes[4], 61 | seedBytes[5], 62 | ]; 63 | } 64 | if (clockseq == null) { 65 | clockseq = _clockseq = ((seedBytes[6] << 8) | seedBytes[7]) & 0x3fff; 66 | } 67 | } 68 | let msecs = 69 | options.msecs !== undefined ? options.msecs : new Date().getTime(); 70 | 71 | let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; 72 | 73 | const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; 74 | 75 | if (dt < 0 && options.clockseq === undefined) { 76 | clockseq = (clockseq + 1) & 0x3fff; 77 | } 78 | 79 | if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { 80 | nsecs = 0; 81 | } 82 | 83 | if (nsecs >= 10000) { 84 | throw new Error("Can't create more than 10M uuids/sec"); 85 | } 86 | 87 | _lastMSecs = msecs; 88 | _lastNSecs = nsecs; 89 | _clockseq = clockseq; 90 | 91 | msecs += 12219292800000; 92 | 93 | const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; 94 | b[i++] = (tl >>> 24) & 0xff; 95 | b[i++] = (tl >>> 16) & 0xff; 96 | b[i++] = (tl >>> 8) & 0xff; 97 | b[i++] = tl & 0xff; 98 | 99 | const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff; 100 | b[i++] = (tmh >>> 8) & 0xff; 101 | b[i++] = tmh & 0xff; 102 | 103 | b[i++] = ((tmh >>> 24) & 0xf) | 0x10; 104 | b[i++] = (tmh >>> 16) & 0xff; 105 | 106 | b[i++] = (clockseq >>> 8) | 0x80; 107 | 108 | b[i++] = clockseq & 0xff; 109 | 110 | for (let n = 0; n < 6; ++n) { 111 | b[i + n] = node[n]; 112 | } 113 | 114 | return buf ? buf : bytesToUuid(b); 115 | } 116 | -------------------------------------------------------------------------------- /std/uuid/v4.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | import { bytesToUuid } from "./common.ts"; 3 | 4 | const UUID_RE = new RegExp( 5 | "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", 6 | "i" 7 | ); 8 | /** 9 | * Validates the UUID v4 10 | * @param id UUID value 11 | */ 12 | export function validate(id: string): boolean { 13 | return UUID_RE.test(id); 14 | } 15 | /** Generates a RFC4122 v4 UUID (pseudo-randomly-based) */ 16 | export function generate(): string { 17 | const rnds = crypto.getRandomValues(new Uint8Array(16)); 18 | 19 | rnds[6] = (rnds[6] & 0x0f) | 0x40; // Version 4 20 | rnds[8] = (rnds[8] & 0x3f) | 0x80; // Variant 10 21 | 22 | return bytesToUuid(rnds); 23 | } 24 | -------------------------------------------------------------------------------- /std/uuid/v5.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | import { 3 | bytesToUuid, 4 | createBuffer, 5 | stringToBytes, 6 | uuidToBytes, 7 | } from "./common.ts"; 8 | import { Sha1 } from "../hash/sha1.ts"; 9 | 10 | const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 11 | /** 12 | * Validates the UUID v5 13 | * @param id UUID value 14 | */ 15 | export function validate(id: string): boolean { 16 | return UUID_RE.test(id); 17 | } 18 | 19 | interface V5Options { 20 | value: string | number[]; 21 | namespace: string | number[]; 22 | } 23 | /** 24 | * Generates a RFC4122 v5 UUID (SHA-1 namespace-based) 25 | * @param options Can use a namespace and value to creat SHA-1 hash 26 | * @param buf Can allow the UUID to be written in byte-form starting at the offset 27 | * @param offset Index to start writing on the UUID bytes in buffer 28 | */ 29 | export function generate( 30 | options: V5Options, 31 | buf?: number[], 32 | offset?: number 33 | ): string | number[] { 34 | const i = (buf && offset) || 0; 35 | 36 | let { value, namespace } = options; 37 | if (typeof value == "string") { 38 | value = stringToBytes(value as string); 39 | } 40 | 41 | if (typeof namespace == "string") { 42 | namespace = uuidToBytes(namespace as string); 43 | } 44 | 45 | if (namespace.length === 16) 46 | throw new TypeError( 47 | "namespace must be uuid string or an Array of 16 byte values" 48 | ); 49 | 50 | const content = (namespace as number[]).concat(value as number[]); 51 | const bytes = new Sha1().update(createBuffer(content)).digest(); 52 | 53 | bytes[6] = (bytes[6] & 0x0f) | 0x50; 54 | bytes[8] = (bytes[8] & 0x3f) | 0x80; 55 | 56 | if (buf) { 57 | for (let idx = 0; idx < 16; ++idx) { 58 | buf[i + idx] = bytes[idx]; 59 | } 60 | } 61 | 62 | return buf || bytesToUuid(bytes); 63 | } 64 | -------------------------------------------------------------------------------- /testing/bench/fs.js: -------------------------------------------------------------------------------- 1 | Elsa.readFile("main.go"); 2 | -------------------------------------------------------------------------------- /testing/bench/fs_deno.js: -------------------------------------------------------------------------------- 1 | await Deno.readTextFile("main.go"); 2 | -------------------------------------------------------------------------------- /testing/bench/fs_node.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | fs.readFile("main.go", "utf8", function (err) { 4 | if (err) throw err; 5 | }); 6 | -------------------------------------------------------------------------------- /testing/bench/http_deno.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | import { serve } from "https://deno.land/std@0.74.0/http/server.ts"; 3 | 4 | const addr = Deno.args[0] || "127.0.0.1:4500"; 5 | const server = serve(addr); 6 | const body = new TextEncoder().encode("Hello World"); 7 | 8 | console.log(`http://${addr}/`); 9 | for await (const req of server) { 10 | const res = { 11 | body, 12 | headers: new Headers(), 13 | }; 14 | req.respond(res).catch(() => {}); 15 | } 16 | -------------------------------------------------------------------------------- /testing/bench/http_elsa.ts: -------------------------------------------------------------------------------- 1 | const addr = Elsa.args[0] || "127.0.0.1:4501"; 2 | const body = "Hello World"; 3 | 4 | function handler() { 5 | const res = { 6 | body, 7 | status: 200, 8 | }; 9 | return res; 10 | } 11 | 12 | console.log(`http://${addr}/`); 13 | 14 | Elsa.serve(addr, handler); 15 | -------------------------------------------------------------------------------- /testing/bench/pi.js: -------------------------------------------------------------------------------- 1 | let i = 1n; 2 | let x = 3n * 10n ** 10020n; 3 | let pi = x; 4 | 5 | while (x > 0) { 6 | x = (x * i) / ((i + 1n) * 4n); 7 | pi += x / (i + 2n); 8 | i += 2n; 9 | } 10 | 11 | const dg = pi / 10n ** 20n; 12 | console.log(dg); 13 | -------------------------------------------------------------------------------- /testing/bundle/basics.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return "test"; 3 | } 4 | 5 | console.log("Elsa 1"); 6 | 7 | console.log("Elsa 2"); 8 | 9 | console.log("Elsa 3"); 10 | 11 | console.log({ a: { b: 1 } }); 12 | 13 | const arrow = () => { 14 | return 1; 15 | }; 16 | 17 | arrow(); // 1 18 | test(); // "test" 19 | -------------------------------------------------------------------------------- /testing/bundle/basics.js.mini.out: -------------------------------------------------------------------------------- 1 | (()=>{function o(){return"test"}console.log("Elsa 1");console.log("Elsa 2");console.log("Elsa 3");console.log({a:{b:1}});const l=()=>1;l();o();})(); 2 | -------------------------------------------------------------------------------- /testing/bundle/basics.js.out: -------------------------------------------------------------------------------- 1 | (() => { 2 | // bundle/basics.js 3 | function test() { 4 | return "test"; 5 | } 6 | console.log("Elsa 1"); 7 | console.log("Elsa 2"); 8 | console.log("Elsa 3"); 9 | console.log({a: {b: 1}}); 10 | const arrow = () => { 11 | return 1; 12 | }; 13 | arrow(); 14 | test(); 15 | })(); 16 | 17 | -------------------------------------------------------------------------------- /testing/bundle/export.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return 1; 3 | } 4 | 5 | export function hello(name) { 6 | console.log(`Hello ${name}`); 7 | } 8 | 9 | export const Name = "Elsa"; 10 | -------------------------------------------------------------------------------- /testing/bundle/hello.ts: -------------------------------------------------------------------------------- 1 | var x: string = "variable"; 2 | console.log(1); 3 | -------------------------------------------------------------------------------- /testing/bundle/hello.ts.out: -------------------------------------------------------------------------------- 1 | (() => { 2 | // bundle/hello.ts 3 | console.log(1); 4 | })(); 5 | 6 | -------------------------------------------------------------------------------- /testing/bundle/local_imports.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elsaland/elsa/1982c3d1cad0a8ed4160b52d68c7c941445a8fa5/testing/bundle/local_imports.js -------------------------------------------------------------------------------- /testing/bundle/local_imports.js.out: -------------------------------------------------------------------------------- 1 | (() => {})(); 2 | -------------------------------------------------------------------------------- /testing/bundle/served.ts: -------------------------------------------------------------------------------- 1 | export function hello(name: string) { 2 | console.log(name); 3 | } 4 | -------------------------------------------------------------------------------- /testing/bundle/url.ts: -------------------------------------------------------------------------------- 1 | import { hello } from "http://localhost:8100/bundle/served.ts"; 2 | hello("Elsa"); 3 | -------------------------------------------------------------------------------- /testing/bundle_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/elsaland/elsa/bundler" 9 | "github.com/elsaland/elsa/module" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | type bundleTestDesc struct { 14 | name string 15 | path string 16 | category string 17 | minify bool 18 | } 19 | 20 | var config = &module.Config{} 21 | 22 | var TestDesc = []bundleTestDesc{ 23 | { 24 | "Bundle no-import js module", 25 | "bundle/basics.js", 26 | "vanilla", 27 | false, 28 | }, 29 | { 30 | "Bundle local js module", 31 | "bundle/local_imports.js", 32 | "es", 33 | false, 34 | }, 35 | { 36 | "Bundle no-import ts module", 37 | "bundle/hello.ts", 38 | "ts", 39 | false, 40 | }, 41 | { 42 | "Bundle URL import", 43 | "bundle/url.ts", 44 | "url", 45 | false, 46 | }, 47 | { 48 | "Bundle minified", 49 | "bundle/basics.js", 50 | "vanilla", 51 | true, 52 | }, 53 | } 54 | 55 | // utility method to read the expected output for a particular test file 56 | func readOutData(sourceFile string, minified bool) string { 57 | path := sourceFile 58 | if minified { 59 | path += ".mini.out" 60 | } else { 61 | path += ".out" 62 | } 63 | dat, e := ioutil.ReadFile(path) 64 | if e != nil { 65 | panic(e) 66 | } 67 | return string(dat) 68 | } 69 | 70 | func TestBundle(t *testing.T) { 71 | // Start the local test server in a seperate goroutine. 72 | go StartTestServer() 73 | r := strings.NewReplacer("\n", "", "\r", "") 74 | 75 | for _, tst := range TestDesc { 76 | // Passing Test 77 | t.Run(tst.name, func(t *testing.T) { 78 | assert := assert.New(t) 79 | // Since URL bundle outputs may differ depending upon the if the import is cached or not 80 | // therefore, we only check whether it didn't exit with a bad status code. 81 | // TODO: we might want to do wildcard based assertion 82 | if tst.category == "url" { 83 | bundle := bundler.BundleModule(tst.path, false, config) 84 | 85 | assert.NotNil(bundle) 86 | } else { 87 | bundle := bundler.BundleModule(tst.path, tst.minify, config) 88 | expected := readOutData(tst.path, tst.minify) 89 | 90 | // Remove newlines from the out data and bundle and assert 91 | assert.Equal(r.Replace(bundle), r.Replace(expected)) 92 | } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /testing/core_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elsaland/elsa/core" 7 | "github.com/elsaland/elsa/core/options" 8 | "github.com/elsaland/elsa/module" 9 | "github.com/elsaland/elsa/util" 10 | ) 11 | 12 | type runTestDesc struct { 13 | name string 14 | source string 15 | } 16 | 17 | var RunTestDesc = []runTestDesc{ 18 | { 19 | "Basic", 20 | "1 + 1", 21 | }, 22 | { 23 | "Bundle local js module", 24 | "Elsa.readFile('fs/sample.txt')", 25 | }, 26 | } 27 | 28 | func TestCore(t *testing.T) { 29 | // Load config 30 | config, err := module.ConfigLoad() 31 | util.Check(err) 32 | for _, tst := range RunTestDesc { 33 | // Passing Test 34 | t.Run(tst.name, func(t *testing.T) { 35 | // Run the test source with filename as test.js, default config and all perms 36 | env := options.Environment{ 37 | NoColor: config.Options.NoColor, 38 | Args: []string{}, 39 | } 40 | opt := options.Options{ 41 | SourceFile: "test.js", 42 | Source: tst.source, 43 | Perms: &options.Perms{Fs: true}, 44 | Env: env, 45 | } 46 | core.Run(opt) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /testing/exports/1.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return 1; 3 | } 4 | -------------------------------------------------------------------------------- /testing/exports/2.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | console.log(1); 3 | } 4 | -------------------------------------------------------------------------------- /testing/exports/import.js: -------------------------------------------------------------------------------- 1 | import X from "./1.js"; 2 | import Y from "./2.js"; 3 | 4 | const __default2 = "test"; 5 | console.warn(X, Y, __default2); 6 | -------------------------------------------------------------------------------- /testing/fs/cwd_test.js: -------------------------------------------------------------------------------- 1 | console.log(Elsa.cwd()); 2 | -------------------------------------------------------------------------------- /testing/fs/exists_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fs - exists (true)": function () { 3 | if (!Elsa.exists("testing/fs/sample.txt")) 4 | throw new Error("Elsa.exists failed"); 5 | }, 6 | "test fs - exists (false)": function () { 7 | if (Elsa.exists("testing/idonotexist.txt")) 8 | throw new Error("Elsa.exists failed"); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /testing/fs/readFile_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fs - readFile": function () { 3 | Elsa.readFile("testing/fs/sample.txt"); 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /testing/fs/remove_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fs - remove": function () { 3 | Elsa.writeFile( 4 | "testing/fs/to_remove.txt", 5 | ` 6 | I am written by Elsa.writeFile 7 | ...and then removed by Elsa.writeFile 8 | ` 9 | ); 10 | 11 | Elsa.remove("testing/fs/to_remove.txt"); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /testing/fs/sample.txt: -------------------------------------------------------------------------------- 1 | Sample file written using Elsa -------------------------------------------------------------------------------- /testing/fs/stats_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fs - stat": function () { 3 | Elsa.stat("testing/fs/sample.txt"); 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /testing/fs/writeFile_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fs - writeFile": function () { 3 | Elsa.writeFile("testing/fs/sample.txt", "Sample file written using Elsa"); 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /testing/http/serve.js: -------------------------------------------------------------------------------- 1 | function handler(req) { 2 | console.log(req); 3 | console.log(`Method: ${req.Method}\nPath: ${req.Path}`); 4 | return { body: "Hello from Elsa :)", status: 200 }; 5 | } 6 | 7 | Elsa.serve(":8080", handler); 8 | -------------------------------------------------------------------------------- /testing/miscellaneous/args.js: -------------------------------------------------------------------------------- 1 | // WARNING! pkg_test will fail if you move this file. 2 | console.log(Elsa.args); 3 | -------------------------------------------------------------------------------- /testing/miscellaneous/args_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test Elsa.args": function () { 3 | if (!Elsa.args || typeof Elsa.args == "undefined") { 4 | throw new Error("Elsa.args is undefined"); 5 | } 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /testing/miscellaneous/mode_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test Elsa.mode": function () { 3 | if (!Elsa.mode || typeof Elsa.mode == "undefined") { 4 | throw new Error("Elsa.mode is undefined"); 5 | } 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /testing/miscellaneous/while.js: -------------------------------------------------------------------------------- 1 | while (true) { 2 | console.log("Elsa"); 3 | } 4 | -------------------------------------------------------------------------------- /testing/ops/event.js: -------------------------------------------------------------------------------- 1 | var ee = new EventEmitter(); 2 | ee.defineEvents(["01", "02", "03"]); 3 | 4 | ee.addListener("01", (x) => { 5 | console.log(x); 6 | }); 7 | 8 | ee.emitEvent("01", ["some emitted data"]); 9 | -------------------------------------------------------------------------------- /testing/ops/pending_jobs.js: -------------------------------------------------------------------------------- 1 | // TODO(@qu4k): remove when tla is supported 2 | (async () => { 3 | const promise = new Promise((resolve) => { 4 | resolve(anotherHello()); 5 | }); 6 | console.log(await await promise); 7 | })(); 8 | 9 | async function anotherHello() { 10 | const promise = new Promise((resolve) => { 11 | resolve("Hello World"); 12 | }); 13 | return promise; 14 | } 15 | -------------------------------------------------------------------------------- /testing/pkg_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elsaland/elsa/packager" 7 | ) 8 | 9 | func TestPkg(t *testing.T) { 10 | packager.PkgSource("miscellaneous/args.js") 11 | } 12 | -------------------------------------------------------------------------------- /testing/plugin/plugin.js: -------------------------------------------------------------------------------- 1 | Elsa.runPlugin("./test_plugin/plugin.so", "Elsa"); 2 | -------------------------------------------------------------------------------- /testing/plugin/test_plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var V interface{} 6 | 7 | func ElsaPlugin() interface{} { 8 | fmt.Printf("Hello, %s\n", V) 9 | return "Some returned data" 10 | } 11 | -------------------------------------------------------------------------------- /testing/test_server.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | // StartTestServer start a new local test server for bundler tests. 9 | func StartTestServer() { 10 | http.Handle("/", http.FileServer(http.Dir("."))) 11 | log.Fatal(http.ListenAndServe(":8100", nil)) 12 | } 13 | -------------------------------------------------------------------------------- /testing/tests/mode_test.js: -------------------------------------------------------------------------------- 1 | import { eq } from "./utils.ts"; 2 | 3 | Elsa.tests({ 4 | "test mode == `test`": function () { 5 | eq(Elsa.mode, "test"); 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /testing/tests/test1_test.js: -------------------------------------------------------------------------------- 1 | import { add, eq } from "./utils.ts"; 2 | 3 | Elsa.tests({ 4 | "adds numbers #2": function () { 5 | eq(6, add(2, 4)); 6 | eq(6.6, add(2.6, 4)); 7 | }, 8 | 9 | "subtracts numbers #2": function () { 10 | eq(-2, add(2, -4)); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /testing/tests/test_test.js: -------------------------------------------------------------------------------- 1 | import { add, eq } from "./utils.ts"; 2 | 3 | Elsa.tests({ 4 | "adds numbers": function () { 5 | eq(6, add(2, 4)); 6 | eq(6.6, add(2.6, 4)); 7 | }, 8 | 9 | "subtracts numbers": function () { 10 | eq(-2, add(2, -4)); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /testing/tests/utils.ts: -------------------------------------------------------------------------------- 1 | // Utility functions for testing the test runner ;) 2 | // (also tests imports...for some reason it didn't feel good without them) 3 | 4 | export function add(a: number, b: number) { 5 | return a + b; 6 | } 7 | 8 | export function eq(a: number | string | boolean, b: number | string | boolean) { 9 | if (!deepEqual(a, b)) { 10 | throw new Error(`Assertion failed: ${a} !== ${b}\n`); 11 | } 12 | } 13 | 14 | export function deepEqual(a: any, b: any) { 15 | if (Object.is(a, b)) { 16 | // items are identical 17 | return true; 18 | } else if ( 19 | typeof a === "object" && 20 | a !== null && 21 | typeof b === "object" && 22 | b !== null 23 | ) { 24 | // items are objects - do a deep property value compare 25 | // join keys from both objects together in one array 26 | let keys = Object.keys(a).concat(Object.keys(b)); 27 | // filter out duplicate keys 28 | keys = keys.filter(function (value, index, self) { 29 | return self.indexOf(value) === index; 30 | }); 31 | for (const p of keys) { 32 | if (typeof a[p] === "object" && typeof b[p] === "object") { 33 | if (!deepEqual(a[p], b[p])) { 34 | return false; 35 | } 36 | } else if (a[p] !== b[p]) { 37 | return false; 38 | } 39 | } 40 | return true; 41 | } else { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testing/typescript/import.ts: -------------------------------------------------------------------------------- 1 | import * as abc from "./typeerror.ts"; 2 | import * as bc from "./wrong.ts"; 3 | import { hello } from "https://x.nest.land/arweave-hello@0.0.2/mod.ts"; 4 | 5 | hello("Elsa"); 6 | console.log(abc, bc) -------------------------------------------------------------------------------- /testing/typescript/typeerror.ts: -------------------------------------------------------------------------------- 1 | const str: string = 1; 2 | 3 | console.oops() 4 | 5 | const y = 1 6 | y += 1 7 | 8 | export const blabla = 1; -------------------------------------------------------------------------------- /testing/typescript/wrong.ts: -------------------------------------------------------------------------------- 1 | var y: string = 1; 2 | -------------------------------------------------------------------------------- /testing/web/console_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test console.log - string": function () { 3 | console.log("This is a string"); 4 | }, 5 | "test console.log - number": function () { 6 | console.log(12345); 7 | }, 8 | "test console.log (number + string)": function () { 9 | console.log(1 + " is a string!"); 10 | }, 11 | "test console.log (nested object)": function () { 12 | console.log({ 13 | super: { 14 | nested: { 15 | object: { 16 | it: { 17 | is: { 18 | indeed: true, 19 | }, 20 | }, 21 | }, 22 | }, 23 | }, 24 | }); 25 | }, 26 | "test console.log - array with nested types": function () { 27 | console.log([ 28 | 1, 29 | 2, 30 | 3, 31 | "woop, a string", 32 | { 33 | oops: { 34 | coords: [ 35 | { 36 | x: 1, 37 | y: 2, 38 | }, 39 | { 40 | x: 2, 41 | y: 1, 42 | }, 43 | ], 44 | }, 45 | }, 46 | ["umm", "nested", ["arrays"]], 47 | ]); 48 | }, 49 | "test console.log - functions": function () { 50 | console.log(console.log); 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /testing/web/encode_test.js: -------------------------------------------------------------------------------- 1 | const encoder = new TextEncoder(); 2 | const decoder = new TextDecoder(); 3 | import { eq } from "../tests/utils.ts"; 4 | 5 | const text = "Elsa"; 6 | 7 | Elsa.tests({ 8 | "encode & decode": function () { 9 | const encoded = encoder.encode(text); 10 | const decoded = decoder.decode(encoded); 11 | eq(text, decoded); 12 | }, 13 | "atob & btoa": function () { 14 | eq(btoa("Hello, world!"), "SGVsbG8sIHdvcmxkIQ=="); 15 | eq(atob("SGVsbG8sIHdvcmxkIQ=="), "Hello, world!"); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /testing/web/fetch_test.js: -------------------------------------------------------------------------------- 1 | Elsa.tests({ 2 | "test fetch API": async function () { 3 | try { 4 | fetch("https://google.com"); 5 | } catch (e) { 6 | throw new Error(e); 7 | } 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /util/check.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/elsaland/quickjs" 9 | ) 10 | 11 | func Check(err error) { 12 | if err != nil { 13 | var evalErr *quickjs.Error 14 | if errors.As(err, &evalErr) { 15 | fmt.Println(evalErr.Cause) 16 | fmt.Println(evalErr.Stack) 17 | } 18 | Panic(err) 19 | } 20 | } 21 | 22 | // Panic pretty print the error and exit with status code 1 23 | func Panic(err error) { 24 | LogError("Error", fmt.Sprintf("%v", err)) 25 | os.Exit(1) 26 | } 27 | -------------------------------------------------------------------------------- /util/log.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | ) 7 | 8 | func LogError(scope, format string, a ...interface{}) { 9 | fmt.Fprintf(color.Output, "%s: %s\n", color.New(color.FgRed, color.Bold).Sprint(scope), fmt.Sprintf(format, a...)) 10 | } 11 | 12 | func LogInfo(scope, format string, a ...interface{}) { 13 | fmt.Fprintf(color.Output, "%s: %s\n", color.New(color.FgGreen, color.Bold).Sprint(scope), fmt.Sprintf(format, a...)) 14 | } 15 | --------------------------------------------------------------------------------