mirror of
https://github.com/nlepage/go-wasm-http-server.git
synced 2026-01-12 10:09:12 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f3e39bf97 | ||
|
|
065e91545b | ||
|
|
9be33ecb4d | ||
|
|
6ddad6298b | ||
|
|
1f38d08145 | ||
|
|
5dc06a3dbb | ||
|
|
38c1814f66 | ||
|
|
268c971467 | ||
|
|
57a311369e | ||
|
|
5f2b342f3a | ||
|
|
8e787fbf29 | ||
|
|
ce6765e72a | ||
|
|
d12a255cff | ||
|
|
a16a847b26 | ||
|
|
3cf36c41e2 | ||
|
|
30a6ef67f9 | ||
|
|
669f82020d | ||
|
|
c94dcd965d | ||
|
|
2d786bdb14 | ||
|
|
c561826125 | ||
|
|
897626b7d1 | ||
|
|
c93d379f20 | ||
|
|
98257b470a | ||
|
|
b2bd8679fd | ||
|
|
770d49a106 | ||
|
|
e8555180f7 | ||
|
|
8abad8cb77 | ||
|
|
74cbaf89b5 | ||
|
|
23cde9d811 | ||
|
|
b7e5adfd23 | ||
|
|
3220c94fa5 | ||
|
|
5ec4a8d7e8 |
36
.all-contributorsrc
Normal file
36
.all-contributorsrc
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"projectName": "go-wasm-http-server",
|
||||
"projectOwner": "nlepage",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"commitConvention": "gitmoji",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "jphastings",
|
||||
"name": "JP Hastings-Edrei",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42999?v=4",
|
||||
"profile": "https://byjp.me/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc",
|
||||
"example"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "EliCDavis",
|
||||
"name": "Eli Davis",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9094977?v=4",
|
||||
"profile": "https://recolude.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"linkToUsage": false
|
||||
}
|
||||
2
LICENSE
2
LICENSE
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 Nicolas Lepage
|
||||
Copyright 2025 Nicolas Lepage
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
76
README.md
76
README.md
@@ -6,9 +6,6 @@
|
||||
<a href="https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE" target="_blank">
|
||||
<img alt="License: Apache 2.0" src="https://img.shields.io/badge/License-Apache--2.0-yellow.svg" />
|
||||
</a>
|
||||
<a href="https://twitter.com/njblepage" target="_blank">
|
||||
<img alt="Twitter: njblepage" src="https://img.shields.io/twitter/follow/njblepage.svg?style=social" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
> Embed your Go HTTP handlers in a ServiceWorker (using [WebAssembly](https://mdn.io/WebAssembly/)) and emulate an HTTP server!
|
||||
@@ -18,9 +15,10 @@
|
||||
- [Hello example](https://nlepage.github.io/go-wasm-http-server/hello) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello))
|
||||
- [Hello example with state](https://nlepage.github.io/go-wasm-http-server/hello-state) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state))
|
||||
- [Hello example with state and keepalive](https://nlepage.github.io/go-wasm-http-server/hello-state-keepalive) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state-keepalive))
|
||||
- [Hello example with Server Sent Events](https://nlepage.github.io/go-wasm-http-server/hello-sse/) ([sources](https://nlepage.github.io/go-wasm-http-server/hello-sse/))
|
||||
- [😺 Catption generator example](https://nlepage.github.io/catption/wasm) ([sources](https://github.com/nlepage/catption/tree/wasm))
|
||||
- [Random password generator web server](https://nlepage.github.io/random-password-please/) ([sources](https://github.com/nlepage/random-password-please) forked from [jbarham/random-password-please](https://github.com/jbarham/random-password-please))
|
||||
|
||||
- [Server fallbacks, and compiling with TinyGo](https://nlepage.github.io/go-wasm-http-server/tinygo/) (runs locally; see [sources & readme](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/tinygo#readme) for how to run this example)
|
||||
|
||||
## How?
|
||||
|
||||
@@ -39,6 +37,7 @@ The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/).
|
||||
`go-wasm-http-server` requires you to build your Go application to WebAssembly, so you need to make sure your code is compatible:
|
||||
- no C bindings
|
||||
- no System dependencies such as file system or network (database server for example)
|
||||
- For smaller WASM blobs, your code may also benefit from being compatible with, and compiled by, [TinyGo](https://tinygo.org/docs/reference/lang-support/stdlib/). See the TinyGo specific details below.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -72,7 +71,7 @@ becomes:
|
||||
package main
|
||||
|
||||
import (
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server"
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -87,17 +86,37 @@ You may want to use build tags as shown above (or file name suffixes) in order t
|
||||
Then build your WebAssembly binary:
|
||||
|
||||
```sh
|
||||
# To compile with Go
|
||||
GOOS=js GOARCH=wasm go build -o server.wasm .
|
||||
|
||||
# To compile with TinyGo, if your code is compatible
|
||||
GOOS=js GOARCH=wasm tinygo build -o server.wasm .
|
||||
```
|
||||
|
||||
### Step 2: Create ServiceWorker file
|
||||
|
||||
First, check the version of Go/TinyGo you compiled your wasm with:
|
||||
|
||||
```sh
|
||||
$ go version
|
||||
go version go1.23.4 darwin/arm64
|
||||
# ^------^
|
||||
|
||||
$ tinygo version
|
||||
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
|
||||
# ^----^
|
||||
```
|
||||
|
||||
Create a ServiceWorker file with the following code:
|
||||
|
||||
📄 `sw.js`
|
||||
```js
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
// Note the 'go.1.23.4' below, that matches the version you just found:
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.4/misc/wasm/wasm_exec.js')
|
||||
// If you compiled with TinyGo then, similarly, use:
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.0/sw.js')
|
||||
|
||||
registerWasmHTTPListener('path/to/server.wasm')
|
||||
```
|
||||
@@ -144,7 +163,7 @@ For Go API see [pkg.go.dev/github.com/nlepage/go-wasm-http-server](https://pkg.g
|
||||
|
||||
### JavaScript API
|
||||
|
||||
### [`registerWasmHTTPListener(wasmUrl, options)`](https://github.com/nlepage/go-wasm-http-server/blob/v1.0.0/sw.js#L3)
|
||||
### `registerWasmHTTPListener(wasmUrl, options)`
|
||||
|
||||
Instantiates and runs the WebAssembly module at `wasmUrl`, and registers a fetch listener forwarding requests to the WebAssembly module's server.
|
||||
|
||||
@@ -163,15 +182,44 @@ URL string of the WebAssembly module, example: `"path/to/my-module.wasm"`.
|
||||
An optional object containing:
|
||||
|
||||
- `base` (`string`): Base path of the server, relative to the ServiceWorker's scope.
|
||||
- `cacheName` (`string`): Name of the [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to store the WebAssembly binary.
|
||||
- `args` (`string[]`): Arguments for the WebAssembly module.
|
||||
- `passthrough` (`(request: Request): boolean`): Optional callback to allow passing the request through to network.
|
||||
|
||||
## Author
|
||||
## <abbr title="Frequently Asked Questions">FAQ</abbr> ❓
|
||||
|
||||
👤 **Nicolas Lepage**
|
||||
### Are WebSockets supported?
|
||||
|
||||
* Website: https://nicolas.lepage.dev/
|
||||
* Twitter: [@njblepage](https://twitter.com/njblepage)
|
||||
* Github: [@nlepage](https://github.com/nlepage)
|
||||
No, WebSockets aren’t and won’t be supported, because Service Workers cannot intercept websocket connections.
|
||||
|
||||
However [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), which is an alternative to WebSockets, are supported, you can find the code for an example [here](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-sse) and the demo [here](https://nlepage.github.io/go-wasm-http-server/hello-sse/).
|
||||
|
||||
### Is it compatible with TinyGo?
|
||||
|
||||
Yes, an example and some specific information is available [here](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/tinygo).
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://byjp.me/"><img src="https://avatars.githubusercontent.com/u/42999?v=4?s=100" width="100px;" alt="JP Hastings-Edrei"/><br /><sub><b>JP Hastings-Edrei</b></sub></a><br /><a href="https://github.com/nlepage/go-wasm-http-server/commits?author=jphastings" title="Code">💻</a> <a href="https://github.com/nlepage/go-wasm-http-server/commits?author=jphastings" title="Documentation">📖</a> <a href="#example-jphastings" title="Examples">💡</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://recolude.com/"><img src="https://avatars.githubusercontent.com/u/9094977?v=4?s=100" width="100px;" alt="Eli Davis"/><br /><sub><b>Eli Davis</b></sub></a><br /><a href="https://github.com/nlepage/go-wasm-http-server/commits?author=EliCDavis" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
@@ -183,7 +231,7 @@ Give a ⭐️ if this project helped you!
|
||||
|
||||
## 📝 License
|
||||
|
||||
Copyright © 2021 [Nicolas Lepage](https://github.com/nlepage).<br />
|
||||
Copyright © 2025 [Nicolas Lepage](https://github.com/nlepage).<br />
|
||||
This project is [Apache 2.0](https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE) licensed.
|
||||
|
||||
***
|
||||
|
||||
34
docs/hello-sse/api.go
Normal file
34
docs/hello-sse/api.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tmaxmax/go-sse"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := &sse.Server{}
|
||||
t, _ := sse.NewType("ping")
|
||||
|
||||
go func() {
|
||||
m := &sse.Message{
|
||||
Type: t,
|
||||
}
|
||||
m.AppendData("Hello world")
|
||||
|
||||
for range time.Tick(time.Second) {
|
||||
_ = s.Publish(m)
|
||||
}
|
||||
}()
|
||||
|
||||
http.Handle("/events", s)
|
||||
|
||||
if _, err := wasmhttp.Serve(nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
BIN
docs/hello-sse/api.wasm
Executable file
BIN
docs/hello-sse/api.wasm
Executable file
Binary file not shown.
57
docs/hello-sse/index.html
Normal file
57
docs/hello-sse/index.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>go-wasm-http-server hello sse demo</title>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
.then(registration => {
|
||||
const serviceWorker = registration.installing ?? registration.waiting ?? registration.active
|
||||
if (serviceWorker.state === 'activated') {
|
||||
document.querySelector('#open-button').disabled = false
|
||||
} else {
|
||||
serviceWorker.addEventListener('statechange', e => {
|
||||
if (e.target.state === 'activated') {
|
||||
document.querySelector('#open-button').disabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
let es;
|
||||
|
||||
document.querySelector('#open-button').addEventListener('click', () => {
|
||||
if (es && es.readyState === es.OPEN) return
|
||||
es = new EventSource('api/events')
|
||||
es.addEventListener('ping', (e) => {
|
||||
const li = document.createElement('li')
|
||||
li.textContent = `ping: data=${e.data}`
|
||||
document.querySelector('ul').append(li)
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelector('#close-button').addEventListener('click', () => {
|
||||
if (!es) return
|
||||
es.close()
|
||||
})
|
||||
|
||||
document.querySelector('#clear-button').addEventListener('click', () => {
|
||||
document.querySelector('ul').innerHTML = ''
|
||||
})
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
if (!es) return
|
||||
es.close()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<button id="open-button" disabled>Open event source</button>
|
||||
<button id="close-button">Close event source</button>
|
||||
<button id="clear-button">Clear events</button>
|
||||
</p>
|
||||
<ul></ul>
|
||||
</body>
|
||||
</html>
|
||||
14
docs/hello-sse/sw.js
Normal file
14
docs/hello-sse/sw.js
Normal file
@@ -0,0 +1,14 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.0/sw.js')
|
||||
|
||||
const wasm = 'api.wasm'
|
||||
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', (event) => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>go-wasm-http-server hello with state demo</title>
|
||||
<title>go-wasm-http-server hello with state and keepalive demo</title>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw.js').then(registration => {
|
||||
const sw = registration.active ?? registration.installing ?? registration.waiting
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js')
|
||||
importScripts('../sw.js')
|
||||
|
||||
const wasm = '../hello-state/api.wasm'
|
||||
|
||||
addEventListener('install', event => {
|
||||
event.waitUntil(skipWaiting())
|
||||
event.waitUntil(caches.open('hello-state').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', event => {
|
||||
@@ -11,4 +13,4 @@ addEventListener('activate', event => {
|
||||
|
||||
addEventListener('message', () => {})
|
||||
|
||||
registerWasmHTTPListener('../hello-state/api.wasm', { base: 'api' })
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server"
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,14 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.0/sw.js')
|
||||
|
||||
const wasm = 'api.wasm'
|
||||
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(skipWaiting())
|
||||
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', event => {
|
||||
addEventListener('activate', (event) => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
|
||||
registerWasmHTTPListener('api.wasm', { base: 'api' })
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server"
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,14 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.0/sw.js')
|
||||
|
||||
const wasm = 'api.wasm'
|
||||
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(skipWaiting())
|
||||
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', event => {
|
||||
addEventListener('activate', (event) => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
|
||||
registerWasmHTTPListener('api.wasm', { base: 'api' })
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
||||
|
||||
16
docs/index.html
Normal file
16
docs/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>go-wasm-http-server examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>go-wasm-http-server examples</h1>
|
||||
<ul>
|
||||
<li><a href="hello/">Hello example</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello">Sources</a>)</li>
|
||||
<li><a href="hello-state/">Hello example with state</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state">Sources</a>)</li>
|
||||
<li><a href="hello-state-keepalive/">Hello example with state and keepalive</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state-keepalive">Sources</a>)</li>
|
||||
<li><a href="hello-sse/">Hello example with Server Sent Events</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-sse">Sources</a>)</li>
|
||||
</ul>
|
||||
<p>See <a href="https://github.com/nlepage/go-wasm-http-server?tab=readme-ov-file#readme">README</a> for more information.</p>
|
||||
</body>
|
||||
</html>
|
||||
42
docs/tinygo/README.md
Normal file
42
docs/tinygo/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Compiling with TinyGo
|
||||
|
||||
This example demonstrates that go-wasm-http-server can also be compiled with [TinyGo](https://www.tinygo.org), producing significantly smaller WASM blobs, though at the expense of [at least one known bug](https://github.com/tinygo-org/tinygo/issues/1140) and a [reduced standard library](https://tinygo.org/docs/reference/lang-support/stdlib/).
|
||||
|
||||
This example also demonstrates how the same code can be used for both server-side execution, and client-side execution in WASM (providing support for clients that cannot interpret WASM).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You'll need a version of [TinyGo installed](https://tinygo.org/getting-started/install/). (eg. `brew install tinygo-org/tools/tinygo`)
|
||||
|
||||
You'll need to make sure the first line of `sw.js` here has the same tinygo version number as your TinyGo version (this was v0.35.0 at time of writing).
|
||||
|
||||
## Build & run
|
||||
|
||||
Compile the WASM blob with TinyGo (this has been done for you for this example):
|
||||
|
||||
```bash
|
||||
GOOS=js GOARCH=wasm tinygo build -o api.wasm .
|
||||
```
|
||||
|
||||
Run the server (with Go, not TinyGo):
|
||||
|
||||
```bash
|
||||
$ go run .
|
||||
Server starting on http://127.0.0.1:<port>
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
You **must** use the TinyGo `wasm_exec.js`, specific to the version of TinyGo used to compile the WASM, in your `sw.js`. For example, if using the JSDelivr CDN:
|
||||
|
||||
```js
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
```
|
||||
|
||||
Note that the `0.35.0` within the path matches the TinyGo version used:
|
||||
|
||||
```sh
|
||||
$ tinygo version
|
||||
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
|
||||
# ^----^
|
||||
```
|
||||
BIN
docs/tinygo/api.wasm
Normal file
BIN
docs/tinygo/api.wasm
Normal file
Binary file not shown.
19
docs/tinygo/handlers.go
Normal file
19
docs/tinygo/handlers.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func goRuntimeHandler(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(res).Encode(map[string]string{
|
||||
"os": runtime.GOOS,
|
||||
"arch": runtime.GOARCH,
|
||||
"compiler": runtime.Compiler,
|
||||
"version": runtime.Version(),
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
44
docs/tinygo/index.html
Normal file
44
docs/tinygo/index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>go-wasm-http-server tinygo demo</title>
|
||||
<script>
|
||||
const sw = navigator.serviceWorker
|
||||
|
||||
function attachServiceWorker() {
|
||||
sw.register('sw.js')
|
||||
.then(() => {
|
||||
document.getElementById('wasm-status').innerText = "⚡️ Loaded - Will execute WASM locally"
|
||||
})
|
||||
.catch((err) => {
|
||||
document.getElementById('wasm-status').innerText = "🛑 Error loading service worker — Check console"
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
async function makeQuery() {
|
||||
const res = await fetch('api/tiny', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('output').innerText = await res.text()
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>This example demonstrates that go-wasm-http-server can be compiled with <a href="https://www.tinygo.org">TinyGo</em>, producing significantly smaller WASM blobs, at the expense of <a href="https://github.com/tinygo-org/tinygo/issues/1140">at least one known bug</a>, and a <a href="https://tinygo.org/docs/reference/lang-support/stdlib/">reduced standard library</a>.</p>
|
||||
<dl><dt>WASM HTTP Service Worker:</dt><dd id="wasm-status">☁️ Not loaded — will call server</dd></dl>
|
||||
|
||||
<ol>
|
||||
<li><button onclick="makeQuery()">Call API</button></li>
|
||||
<li><button onclick="attachServiceWorker()">Attach the service worker</button></li>
|
||||
<li><span>Call the API again (Step 1)</span></li>
|
||||
</ol>
|
||||
|
||||
<h3>Response:</h3>
|
||||
<pre id="output"></pre>
|
||||
</body>
|
||||
</html>
|
||||
34
docs/tinygo/server.go
Normal file
34
docs/tinygo/server.go
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build !wasm
|
||||
// +build !wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed *.html *.js *.wasm
|
||||
var thisDir embed.FS
|
||||
|
||||
func main() {
|
||||
// Serve all files in this directory statically
|
||||
http.Handle("/", http.FileServer(http.FS(thisDir)))
|
||||
|
||||
// Note that this needs to be mounted at /api/tiny, rather than just /tiny (like in wasm.go)
|
||||
// because the service worker mounts the WASM server at /api (at the end of sw.js)
|
||||
http.HandleFunc("/api/tiny", goRuntimeHandler)
|
||||
|
||||
// Pick any available port. Note that ServiceWorkers _require_ localhost for non-SSL serving (so other LAN/WAN IPs will prevent the service worker from loading)
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to claim a port to start server on: %v", err)
|
||||
}
|
||||
|
||||
// Share the port being used & start
|
||||
fmt.Printf("Server starting on http://127.0.0.1:%d\n", listener.Addr().(*net.TCPAddr).Port)
|
||||
panic(http.Serve(listener, nil))
|
||||
}
|
||||
14
docs/tinygo/sw.js
Normal file
14
docs/tinygo/sw.js
Normal file
@@ -0,0 +1,14 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.0/sw.js')
|
||||
|
||||
const wasm = 'api.wasm'
|
||||
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', (event) => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
||||
18
docs/tinygo/wasm.go
Normal file
18
docs/tinygo/wasm.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build wasm
|
||||
// +build wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/tiny", goRuntimeHandler)
|
||||
|
||||
wasmhttp.Serve(nil)
|
||||
|
||||
select {}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server"
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
// Demonstrates a simple hello JSON service.
|
||||
@@ -23,7 +23,11 @@ func Example_json() {
|
||||
}
|
||||
})
|
||||
|
||||
defer wasmhttp.Serve(nil)()
|
||||
release, err := wasmhttp.Serve(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer release()
|
||||
|
||||
// Wait for webpage event or use empty select{}
|
||||
}
|
||||
|
||||
10
go.mod
10
go.mod
@@ -1,5 +1,9 @@
|
||||
module github.com/nlepage/go-wasm-http-server
|
||||
module github.com/nlepage/go-wasm-http-server/v2
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require github.com/nlepage/go-js-promise v1.0.0
|
||||
require (
|
||||
github.com/hack-pad/safejs v0.1.1
|
||||
github.com/nlepage/go-js-promise v1.0.0
|
||||
github.com/tmaxmax/go-sse v0.8.0
|
||||
)
|
||||
|
||||
7
go.sum
7
go.sum
@@ -1,2 +1,9 @@
|
||||
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
|
||||
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ=
|
||||
github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
|
||||
github.com/tmaxmax/go-sse v0.8.0 h1:pPpTgyyi1r7vG2o6icebnpGEh3ebcnBXqDWkb7aTofs=
|
||||
github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
|
||||
13
internal/jstype/types.go
Normal file
13
internal/jstype/types.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package jstype
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
var (
|
||||
ReadableStream = safejs.Safe(js.Global().Get("ReadableStream"))
|
||||
Response = safejs.Safe(js.Global().Get("Response"))
|
||||
Uint8Array = safejs.Safe(js.Global().Get("Uint8Array"))
|
||||
)
|
||||
96
internal/readablestream/reader.go
Normal file
96
internal/readablestream/reader.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package readablestream
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
promise "github.com/nlepage/go-js-promise"
|
||||
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
value safejs.Value
|
||||
buf []byte
|
||||
off int
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = (*Reader)(nil)
|
||||
|
||||
func NewReader(r safejs.Value) *Reader {
|
||||
return &Reader{
|
||||
value: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
if r.off < len(r.buf) {
|
||||
n := copy(p, r.buf[r.off:])
|
||||
|
||||
r.off += n
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
r.off = 0
|
||||
|
||||
pRes, err := r.value.Call("read")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ures, err := promise.Await(safejs.Unsafe(pRes))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
res := safejs.Safe(ures)
|
||||
|
||||
done, err := res.GetBool("done")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if done {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
value, err := res.Get("value")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
l, err := value.GetInt("length")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if cap(r.buf) < l {
|
||||
r.buf = make([]byte, l)
|
||||
}
|
||||
if len(r.buf) < cap(r.buf) {
|
||||
r.buf = r.buf[:cap(r.buf)]
|
||||
}
|
||||
|
||||
n, err := safejs.CopyBytesToGo(r.buf, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.buf = r.buf[:n]
|
||||
|
||||
n = copy(p, r.buf[r.off:])
|
||||
|
||||
r.off += n
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Close() error {
|
||||
p, err := r.value.Call("cancel")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = promise.Await(safejs.Unsafe(p))
|
||||
|
||||
return err
|
||||
}
|
||||
101
internal/readablestream/writer.go
Normal file
101
internal/readablestream/writer.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package readablestream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/jstype"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
Value safejs.Value
|
||||
controller safejs.Value
|
||||
ctx context.Context
|
||||
cancelled bool
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = (*Writer)(nil)
|
||||
|
||||
func NewWriter() (*Writer, error) {
|
||||
var rs *Writer
|
||||
|
||||
var start safejs.Func
|
||||
var controller safejs.Value
|
||||
|
||||
start, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any {
|
||||
defer start.Release()
|
||||
controller = args[0]
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cancel safejs.Func
|
||||
ctx, cancelCtx := context.WithCancel(context.Background())
|
||||
|
||||
cancel, err = safejs.FuncOf(func(_ safejs.Value, _ []safejs.Value) any {
|
||||
defer cancel.Release()
|
||||
rs.cancelled = true
|
||||
cancelCtx()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source, err := safejs.ValueOf(map[string]any{
|
||||
"start": safejs.Unsafe(start.Value()),
|
||||
"cancel": safejs.Unsafe(cancel.Value()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := jstype.ReadableStream.New(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs = &Writer{
|
||||
Value: value,
|
||||
controller: controller,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (rs *Writer) Write(b []byte) (int, error) {
|
||||
if rs.cancelled {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
chunk, err := jstype.Uint8Array.New(len(b)) // FIXME reuse same Uint8Array?
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err := safejs.CopyBytesToJS(chunk, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = rs.controller.Call("enqueue", chunk)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (rs *Writer) Close() error {
|
||||
if rs.cancelled {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := rs.controller.Call("close")
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *Writer) Context() context.Context {
|
||||
return rs.ctx
|
||||
}
|
||||
11
internal/safejs/bytes.go
Normal file
11
internal/safejs/bytes.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package safejs
|
||||
|
||||
import "github.com/hack-pad/safejs"
|
||||
|
||||
func CopyBytesToGo(dst []byte, src Value) (int, error) {
|
||||
return safejs.CopyBytesToGo(dst, safejs.Value(src))
|
||||
}
|
||||
|
||||
func CopyBytesToJS(dst Value, src []byte) (int, error) {
|
||||
return safejs.CopyBytesToJS(safejs.Value(dst), src)
|
||||
}
|
||||
26
internal/safejs/func.go
Normal file
26
internal/safejs/func.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package safejs
|
||||
|
||||
import (
|
||||
"github.com/hack-pad/safejs"
|
||||
)
|
||||
|
||||
type Func safejs.Func
|
||||
|
||||
func FuncOf(fn func(this Value, args []Value) any) (Func, error) {
|
||||
r, err := safejs.FuncOf(func(this safejs.Value, args []safejs.Value) any {
|
||||
args2 := make([]Value, len(args))
|
||||
for i, v := range args {
|
||||
args2[i] = Value(v)
|
||||
}
|
||||
return fn(Value(this), []Value(args2))
|
||||
})
|
||||
return Func(r), err
|
||||
}
|
||||
|
||||
func (f Func) Release() {
|
||||
safejs.Func(f).Release()
|
||||
}
|
||||
|
||||
func (f Func) Value() Value {
|
||||
return Value(safejs.Func(f).Value())
|
||||
}
|
||||
111
internal/safejs/value.go
Normal file
111
internal/safejs/value.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package safejs
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
|
||||
"github.com/hack-pad/safejs"
|
||||
)
|
||||
|
||||
type Value safejs.Value
|
||||
|
||||
func Safe(v js.Value) Value {
|
||||
return Value(safejs.Safe(v))
|
||||
}
|
||||
|
||||
func Unsafe(v Value) js.Value {
|
||||
return safejs.Unsafe(safejs.Value(v))
|
||||
}
|
||||
|
||||
func ValueOf(value any) (Value, error) {
|
||||
v, err := safejs.ValueOf(value)
|
||||
return Value(v), err
|
||||
}
|
||||
|
||||
func (v Value) Call(m string, args ...any) (Value, error) {
|
||||
args = toJSValue(args).([]any)
|
||||
r, err := safejs.Value(v).Call(m, args...)
|
||||
return Value(r), err
|
||||
}
|
||||
|
||||
func (v Value) Get(p string) (Value, error) {
|
||||
r, err := safejs.Value(v).Get(p)
|
||||
return Value(r), err
|
||||
}
|
||||
|
||||
func (v Value) GetBool(p string) (bool, error) {
|
||||
bv, err := v.Get(p)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return safejs.Value(bv).Bool()
|
||||
}
|
||||
|
||||
func (v Value) GetInt(p string) (int, error) {
|
||||
iv, err := v.Get(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return safejs.Value(iv).Int()
|
||||
}
|
||||
|
||||
func (v Value) GetString(p string) (string, error) {
|
||||
sv, err := v.Get(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return safejs.Value(sv).String()
|
||||
}
|
||||
|
||||
func (v Value) Index(i int) (Value, error) {
|
||||
r, err := safejs.Value(v).Index(i)
|
||||
return Value(r), err
|
||||
}
|
||||
|
||||
func (v Value) IndexString(i int) (string, error) {
|
||||
sv, err := v.Index(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return safejs.Value(sv).String()
|
||||
}
|
||||
|
||||
func (v Value) IsNull() bool {
|
||||
return safejs.Value(v).IsNull()
|
||||
}
|
||||
|
||||
func (v Value) IsUndefined() bool {
|
||||
return safejs.Value(v).IsUndefined()
|
||||
}
|
||||
|
||||
func (v Value) New(args ...any) (Value, error) {
|
||||
args = toJSValue(args).([]any)
|
||||
r, err := safejs.Value(v).New(args...)
|
||||
return Value(r), err
|
||||
}
|
||||
|
||||
func toJSValue(jsValue any) any {
|
||||
switch value := jsValue.(type) {
|
||||
case Value:
|
||||
return safejs.Value(value)
|
||||
case Func:
|
||||
return safejs.Func(value)
|
||||
case map[string]any:
|
||||
newValue := make(map[string]any)
|
||||
for mapKey, mapValue := range value {
|
||||
newValue[mapKey] = toJSValue(mapValue)
|
||||
}
|
||||
return newValue
|
||||
case []any:
|
||||
newValue := make([]any, len(value))
|
||||
for i, arg := range value {
|
||||
newValue[i] = toJSValue(arg)
|
||||
}
|
||||
return newValue
|
||||
default:
|
||||
return jsValue
|
||||
}
|
||||
}
|
||||
787
package-lock.json
generated
Normal file
787
package-lock.json
generated
Normal file
@@ -0,0 +1,787 @@
|
||||
{
|
||||
"name": "go-wasm-http-server",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "go-wasm-http-server",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.26.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
||||
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/all-contributors-cli": {
|
||||
"version": "6.26.1",
|
||||
"resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz",
|
||||
"integrity": "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"async": "^3.1.0",
|
||||
"chalk": "^4.0.0",
|
||||
"didyoumean": "^1.2.1",
|
||||
"inquirer": "^7.3.3",
|
||||
"json-fixer": "^1.6.8",
|
||||
"lodash": "^4.11.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"pify": "^5.0.0",
|
||||
"yargs": "^15.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"all-contributors": "dist/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"prettier": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.21.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chardet": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-width": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
|
||||
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer": {
|
||||
"version": "7.3.3",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
|
||||
"integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
"chalk": "^4.1.0",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"cli-width": "^3.0.0",
|
||||
"external-editor": "^3.0.3",
|
||||
"figures": "^3.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
"mute-stream": "0.0.8",
|
||||
"run-async": "^2.4.0",
|
||||
"rxjs": "^6.6.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/json-fixer": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.15.tgz",
|
||||
"integrity": "sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"chalk": "^4.1.2",
|
||||
"pegjs": "^0.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pegjs": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
|
||||
"integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pegjs": "bin/pegjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
||||
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^5.1.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
"integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.21.3",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
// Package wasmhttp (github.com/nlepage/go-wasm-http-server) allows to create a WebAssembly Go HTTP Server embedded in a ServiceWorker.
|
||||
//
|
||||
// It is a subset of the full solution, a full usage is available on the github repository: https://github.com/nlepage/go-wasm-http-server
|
||||
// Package wasmhttp allows to create a WebAssembly Go HTTP Server embedded in a ServiceWorker.
|
||||
package wasmhttp
|
||||
|
||||
@@ -5,5 +5,8 @@
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/nlepage/go-wasm-http-server",
|
||||
"author": "Nicolas Lepage <19571875+nlepage@users.noreply.github.com>",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.26.1"
|
||||
}
|
||||
}
|
||||
|
||||
126
request.go
126
request.go
@@ -1,35 +1,119 @@
|
||||
package wasmhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"syscall/js"
|
||||
|
||||
promise "github.com/nlepage/go-js-promise"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/readablestream"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
// Request builds and returns the equivalent http.Request
|
||||
func Request(r js.Value) *http.Request {
|
||||
jsBody := js.Global().Get("Uint8Array").New(promise.Await(r.Call("arrayBuffer")))
|
||||
body := make([]byte, jsBody.Get("length").Int())
|
||||
js.CopyBytesToGo(body, jsBody)
|
||||
func Request(uvalue js.Value) (*http.Request, error) {
|
||||
value := safejs.Safe(uvalue)
|
||||
|
||||
req := httptest.NewRequest(
|
||||
r.Get("method").String(),
|
||||
r.Get("url").String(),
|
||||
bytes.NewBuffer(body),
|
||||
)
|
||||
|
||||
headersIt := r.Get("headers").Call("entries")
|
||||
for {
|
||||
e := headersIt.Call("next")
|
||||
if e.Get("done").Bool() {
|
||||
break
|
||||
}
|
||||
v := e.Get("value")
|
||||
req.Header.Set(v.Index(0).String(), v.Index(1).String())
|
||||
method, err := value.GetString("method")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req
|
||||
rawURL, err := value.GetString("url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := value.Get("body")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bodyReader io.ReadCloser
|
||||
|
||||
if !body.IsNull() {
|
||||
// WORKAROUND: Firefox does not have request.body ReadableStream
|
||||
if body.IsUndefined() {
|
||||
blobp, err := value.Call("blob")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blob, err := promise.Await(safejs.Unsafe(blobp))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err = safejs.Safe(blob).Call("stream")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
r, err := body.Call("getReader")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bodyReader = readablestream.NewReader(r)
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: method,
|
||||
URL: u,
|
||||
Body: bodyReader,
|
||||
Header: make(http.Header),
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
}
|
||||
|
||||
headers, err := value.Get("headers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headersIt, err := headers.Call("entries")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
e, err := headersIt.Call("next")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done, err := e.GetBool("done")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if done {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := e.Get("value")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := v.IndexString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := v.IndexString(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
175
response.go
Normal file
175
response.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package wasmhttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"syscall/js"
|
||||
|
||||
promise "github.com/nlepage/go-js-promise"
|
||||
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/jstype"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/readablestream"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
http.ResponseWriter
|
||||
io.StringWriter
|
||||
http.Flusher
|
||||
io.Closer
|
||||
Context() context.Context
|
||||
WriteError(string)
|
||||
JSValue() js.Value
|
||||
}
|
||||
|
||||
type response struct {
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
|
||||
promise js.Value
|
||||
resolve func(any)
|
||||
|
||||
rs *readablestream.Writer
|
||||
body *bufio.Writer
|
||||
}
|
||||
|
||||
func NewResponse() (Response, error) {
|
||||
rs, err := readablestream.NewWriter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
promise, resolve, _ := promise.New()
|
||||
|
||||
return &response{
|
||||
promise: promise,
|
||||
resolve: resolve,
|
||||
|
||||
rs: rs,
|
||||
body: bufio.NewWriter(rs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ Response = (*response)(nil)
|
||||
|
||||
// Header implements [http.ResponseWriter].
|
||||
func (r *response) Header() http.Header {
|
||||
if r.header == nil {
|
||||
r.header = make(http.Header)
|
||||
}
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *response) headerValue() map[string]any {
|
||||
h := r.Header()
|
||||
hh := make(map[string]any, len(h)+1)
|
||||
for k := range h {
|
||||
hh[k] = h.Get(k)
|
||||
}
|
||||
return hh
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter.
|
||||
func (r *response) Write(buf []byte) (int, error) {
|
||||
r.writeHeader(buf, "")
|
||||
return r.body.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader implements [http.ResponseWriter].
|
||||
func (r *response) WriteHeader(code int) {
|
||||
if r.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
checkWriteHeaderCode(code)
|
||||
|
||||
init, err := safejs.ValueOf(map[string]any{
|
||||
"status": code,
|
||||
"headers": r.headerValue(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
res, err := jstype.Response.New(r.rs.Value, init)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.wroteHeader = true
|
||||
|
||||
r.resolve(safejs.Unsafe(res))
|
||||
}
|
||||
|
||||
// WriteString implements [io.StringWriter].
|
||||
func (r *response) WriteString(str string) (int, error) {
|
||||
r.writeHeader(nil, str)
|
||||
return r.body.WriteString(str)
|
||||
}
|
||||
|
||||
// Flush implements [http.Flusher]
|
||||
func (r *response) Flush() {
|
||||
if !r.wroteHeader {
|
||||
r.WriteHeader(200)
|
||||
}
|
||||
if err := r.body.Flush(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements [io.Closer]
|
||||
func (r *response) Close() error {
|
||||
if err := r.body.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.rs.Close()
|
||||
}
|
||||
|
||||
func (r *response) Context() context.Context {
|
||||
return r.rs.Context()
|
||||
}
|
||||
|
||||
func (r *response) WriteError(str string) {
|
||||
slog.Error(str)
|
||||
if !r.wroteHeader {
|
||||
r.Header().Set("Content-Type", "text/plain")
|
||||
r.WriteHeader(500)
|
||||
_, _ = r.WriteString(str)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *response) JSValue() js.Value {
|
||||
return r.promise
|
||||
}
|
||||
|
||||
func (r *response) writeHeader(b []byte, str string) {
|
||||
if r.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
m := r.Header()
|
||||
|
||||
_, hasType := m["Content-Type"]
|
||||
hasTE := m.Get("Transfer-Encoding") != ""
|
||||
if !hasType && !hasTE {
|
||||
if b == nil {
|
||||
if len(str) > 512 {
|
||||
str = str[:512]
|
||||
}
|
||||
b = []byte(str)
|
||||
}
|
||||
m.Set("Content-Type", http.DetectContentType(b))
|
||||
}
|
||||
|
||||
r.WriteHeader(200)
|
||||
}
|
||||
|
||||
func checkWriteHeaderCode(code int) {
|
||||
if code < 100 || code > 999 {
|
||||
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package wasmhttp
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// ResponseRecorder uses httptest.ResponseRecorder to build a JS Response
|
||||
type ResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
// NewResponseRecorder returns a new ResponseRecorder
|
||||
func NewResponseRecorder() ResponseRecorder {
|
||||
return ResponseRecorder{httptest.NewRecorder()}
|
||||
}
|
||||
|
||||
// JSResponse builds and returns the equivalent JS Response
|
||||
func (rr ResponseRecorder) JSResponse() js.Value {
|
||||
var res = rr.Result()
|
||||
|
||||
var body js.Value = js.Undefined()
|
||||
if res.ContentLength != 0 {
|
||||
var b, err = ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body = js.Global().Get("Uint8Array").New(len(b))
|
||||
js.CopyBytesToJS(body, b)
|
||||
}
|
||||
|
||||
var init = make(map[string]interface{}, 2)
|
||||
|
||||
if res.StatusCode != 0 {
|
||||
init["status"] = res.StatusCode
|
||||
}
|
||||
|
||||
if len(res.Header) != 0 {
|
||||
var headers = make(map[string]interface{}, len(res.Header))
|
||||
for k := range res.Header {
|
||||
headers[k] = res.Header.Get(k)
|
||||
}
|
||||
init["headers"] = headers
|
||||
}
|
||||
|
||||
return js.Global().Get("Response").New(body, init)
|
||||
}
|
||||
67
serve.go
67
serve.go
@@ -1,57 +1,86 @@
|
||||
package wasmhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
|
||||
promise "github.com/nlepage/go-js-promise"
|
||||
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
|
||||
)
|
||||
|
||||
var (
|
||||
wasmhttp = safejs.Safe(js.Global().Get("wasmhttp"))
|
||||
)
|
||||
|
||||
// Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
|
||||
func Serve(handler http.Handler) func() {
|
||||
var h = handler
|
||||
func Serve(handler http.Handler) (func(), error) {
|
||||
h := handler
|
||||
if h == nil {
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
|
||||
var prefix = js.Global().Get("wasmhttp").Get("path").String()
|
||||
prefix, err := wasmhttp.GetString("path")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for strings.HasSuffix(prefix, "/") {
|
||||
prefix = strings.TrimSuffix(prefix, "/")
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
var mux = http.NewServeMux()
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
|
||||
h = mux
|
||||
}
|
||||
|
||||
var cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
var resPromise, resolve, reject = promise.New()
|
||||
handlerValue, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) interface{} {
|
||||
res, err := NewResponse()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(res.Context())
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err, ok := r.(error); ok {
|
||||
reject(fmt.Sprintf("wasmhttp: panic: %+v\n", err))
|
||||
} else {
|
||||
reject(fmt.Sprintf("wasmhttp: panic: %v\n", r))
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if err := res.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var res = NewResponseRecorder()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
res.WriteError(fmt.Sprintf("%+v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
h.ServeHTTP(res, Request(args[0]))
|
||||
req, err := Request(safejs.Unsafe(args[0]))
|
||||
if err != nil {
|
||||
res.WriteError(fmt.Sprintf("%+v", err))
|
||||
return
|
||||
}
|
||||
|
||||
resolve(res.JSResponse())
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
h.ServeHTTP(res, req)
|
||||
}()
|
||||
|
||||
return resPromise
|
||||
return res.JSValue()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js.Global().Get("wasmhttp").Call("setHandler", cb)
|
||||
if _, err = wasmhttp.Call("setHandler", handlerValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cb.Release
|
||||
return handlerValue.Release, nil
|
||||
}
|
||||
|
||||
12
sw.js
12
sw.js
@@ -1,4 +1,4 @@
|
||||
function registerWasmHTTPListener(wasm, { base, args = [] } = {}) {
|
||||
function registerWasmHTTPListener(wasm, { base, cacheName, passthrough, args = [] } = {}) {
|
||||
let path = new URL(registration.scope).pathname
|
||||
if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}`
|
||||
|
||||
@@ -11,9 +11,17 @@ function registerWasmHTTPListener(wasm, { base, args = [] } = {}) {
|
||||
|
||||
const go = new Go()
|
||||
go.argv = [wasm, ...args]
|
||||
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(({ instance }) => go.run(instance))
|
||||
const source = cacheName
|
||||
? caches.open(cacheName).then((cache) => cache.match(wasm)).then((response) => response ?? fetch(wasm))
|
||||
: caches.match(wasm).then(response => (response) ?? fetch(wasm))
|
||||
WebAssembly.instantiateStreaming(source, go.importObject).then(({ instance }) => go.run(instance))
|
||||
|
||||
addEventListener('fetch', e => {
|
||||
if (passthrough?.(e.request)) {
|
||||
e.respondWith(fetch(e.request))
|
||||
return;
|
||||
}
|
||||
|
||||
const { pathname } = new URL(e.request.url)
|
||||
if (!pathname.startsWith(path)) return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user