51 Commits

Author SHA1 Message Date
Nicolas Lepage
fb62588b31 feat: adds Referer, Host, RequestURI and User-Agent on request 2025-11-21 10:16:13 +01:00
Nicolas Lepage
9ae9f3c443 feat: allows registering multiple wasm instances 2025-11-21 01:04:46 +01:00
Nicolas Lepage
9ff6ec615a docs: updates build tags and adds warning for FOSDEM video 2025-02-13 10:41:03 +01:00
Nicolas Lepage
ac1cfbfe8a chore: v2.2.1 2025-02-07 11:48:06 +01:00
Nicolas Lepage
f0a7bf8f2b docs: updates EliCDavis’ contribution 2025-02-07 11:46:33 +01:00
Nicolas Lepage
d7055fb5ea [BUGFIX] Writes default header when response close and no header already written 2025-02-07 11:45:18 +01:00
Nicolas Lepage
b9921d7d43 fix: writes default header when response closed and no header written 2025-02-07 11:42:55 +01:00
Nicolas Lepage
2f3e39bf97 chore: v2.2.0 2025-02-06 11:21:25 +01:00
Nicolas Lepage
065e91545b docs: adds FAQ mentionning websockets and tinygo 2025-02-06 11:17:08 +01:00
Nicolas Lepage
9be33ecb4d docs: updates examples doc and adds an index for examples 2025-02-06 10:15:25 +01:00
Nicolas Lepage
6ddad6298b docs: adds EliCDavis as a contributor 2025-02-06 10:08:56 +01:00
Nicolas Lepage
1f38d08145 feat: Provide ability to passthrough and hit network 2025-02-06 09:58:37 +01:00
Nicolas Lepage
5dc06a3dbb docs: updates documentation for registerWasmHTTPListener 2025-02-06 09:55:58 +01:00
Nicolas Lepage
38c1814f66 refactor: change signature for passthrough function 2025-02-06 09:55:56 +01:00
Eli Davis
268c971467 Provide ability to passthrough and hit network 2025-02-05 17:48:27 -08:00
Nicolas Lepage
57a311369e doc: credit contributors 2025-01-19 11:29:03 +01:00
Nicolas Lepage
5f2b342f3a chore: v2.1.0 2025-01-19 11:16:06 +01:00
Nicolas Lepage
8e787fbf29 Merge pull request #17 from jphastings/compile-with-tinygo
TinyGo compatible packages
2025-01-19 11:03:24 +01:00
JP Hastings-Spital
ce6765e72a feat: Demonstrates TinyGo compatibility
Adds an example that demonstrates TinyGo compatibility, as well as using a server-side HTTP handler as a fallback.
2025-01-19 11:01:15 +01:00
Nico
d12a255cff implements io.Closer for readablestream.Reader 2024-12-30 00:51:31 +01:00
JP Hastings-Spital
a16a847b26 fix: TinyGo compatible packages
The net/http/httptest package isn't implemented in tinygo — this (minor) change swaps to creating the necessary http.Request struct directly (which needs an extra step or two).

This *is not* enough to get TinyGo _working_ as a compiler for this repo, but the compilation succeeds now.
2024-12-14 10:12:55 +00:00
Nicolas Lepage
3cf36c41e2 chore: v2.0.5 2024-12-09 23:12:31 +01:00
Nicolas Lepage
30a6ef67f9 fix: avoids closing cancelled readablestream 2024-12-09 23:11:17 +01:00
Nicolas Lepage
669f82020d chore: rebuilds hello-sse example 2024-12-09 22:46:00 +01:00
Nicolas Lepage
c94dcd965d chore: v2.0.4 2024-11-27 22:33:50 +01:00
Nicolas Lepage
2d786bdb14 fix: listen for ReadableStream cancellation 2024-11-27 22:32:19 +01:00
Nicolas Lepage
c561826125 chore: v2.0.3 2024-10-17 01:12:34 +02:00
Nicolas Lepage
897626b7d1 fix: Firefox does not have request.body ReadableStream 2024-10-17 01:11:41 +02:00
Nicolas Lepage
c93d379f20 feat: improves examples 2024-10-16 14:01:11 +02:00
Nicolas Lepage
98257b470a feat: allow using cache for wasm binary 2024-10-16 14:01:11 +02:00
Nicolas Lepage
b2bd8679fd fix: error management in readablestream writer close method 2024-10-16 13:19:16 +02:00
Nicolas Lepage
770d49a106 chore: v2.0.2 2024-10-14 23:30:16 +02:00
Nicolas Lepage
e8555180f7 fix: improves error management 2024-10-14 23:28:35 +02:00
Nicolas Lepage
8abad8cb77 fix: crash when request has no body 2024-10-14 23:28:21 +02:00
Nicolas Lepage
74cbaf89b5 fix: wrongful import path in v2.0.0 example 2024-10-14 22:56:59 +02:00
Nicolas Lepage
23cde9d811 chore: v2.0.0 2024-10-14 22:36:22 +02:00
Nicolas Lepage
b7e5adfd23 feat: uses ReadableStream for request (#16) 2024-10-14 22:23:22 +02:00
Nicolas Lepage
3220c94fa5 fix: hello-state-keepalive example crashes 2024-10-14 17:04:06 +02:00
Nicolas Lepage
5ec4a8d7e8 [FEATURE] Use ReadableStream for Response (#15)
* feat: uses ReadableStream for Response

* chore: rebuilds other examples
2024-10-14 09:14:50 +02:00
Nicolas Lepage
163b49702b ⬆️ go 1.18 compatibility 2022-07-28 00:08:56 +02:00
Nicolas Lepage
624ed00220 ♻️ Use github.com/nlepage/go-js-promise 2021-08-16 23:40:53 +02:00
Nicolas Lepage
1f549a4bf0 📝 Create funding.yml 2021-03-02 12:07:53 +01:00
Nicolas Lepage
73a09847ca ✏️ 2021-02-18 22:01:34 +01:00
Nicolas Lepage
0bf86b9d79 📝 2021-02-10 12:44:50 +01:00
Nicolas Lepage
167237a124 📝 2021-02-09 23:20:40 +01:00
Nicolas Lepage
f602159c47 📝 2021-02-09 23:18:59 +01:00
Nicolas Lepage
a06a85731f 📝 New Random password generator web server example 2021-02-08 13:54:49 +01:00
Nicolas Lepage
17e34981b0 📝 2021-02-06 18:15:36 +01:00
Nicolas Lepage
7434774930 📝 2021-01-31 22:24:30 +01:00
Nicolas Lepage
d2e039bd3e 📝 2021-01-31 21:59:22 +01:00
Nicolas Lepage
76abf72cff 📝 2021-01-28 16:00:27 +01:00
52 changed files with 2319 additions and 266 deletions

37
.all-contributorsrc Normal file
View File

@@ -0,0 +1,37 @@
{
"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",
"bug"
]
}
],
"contributorsPerLine": 7,
"linkToUsage": false
}

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: [nlepage]

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all"
}

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"go.toolsEnvVars": {
"GOOS": "js",
"GOARCH": "wasm"
}
}

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2021 Nicolas Lepage Copyright 2025 Nicolas Lepage
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

234
README.md
View File

@@ -6,38 +6,224 @@
<a href="https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE" target="_blank"> <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" /> <img alt="License: Apache 2.0" src="https://img.shields.io/badge/License-Apache--2.0-yellow.svg" />
</a> </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> </p>
> Build your Go HTTP Server to [WebAssembly](https://mdn.io/WebAssembly/) and embed it in a ServiceWorker! > Embed your Go HTTP handlers in a ServiceWorker (using [WebAssembly](https://mdn.io/WebAssembly/)) and emulate an HTTP server!
✨ [Demos](https://nlepage.github.io/go-wasm-http-server/) ## Examples
## Install - [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))
TODO - [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/))
## Usage - [😺 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))
TODO - [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)
## Why?
TODO
## How? ## How?
TODO Below is a talk given at the Go devroom of FOSDEM 2021 explaining how `go-wasm-http-server` works.
## Author > [!WARNING]
> `go-wasm-http-server` has suffered major changes since this talk, be aware that it is not accurate anymore on several aspects.
> Please refer to the documentation below for up to date usage of `go-wasm-http-server`.
👤 **Nicolas Lepage** [![Deploy a Go HTTP server in your browser Youtube link](https://raw.githubusercontent.com/nlepage/go-wasm-http-talk/main/youtube.png)](https://youtu.be/O2RB_8ircdE)
* Website: https://nicolas.lepage.dev/ The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/).
* Twitter: [@njblepage](https://twitter.com/njblepage)
* Github: [@nlepage](https://github.com/nlepage) ## Why?
`go-wasm-http-server` can help you put up a demonstration for a project without actually running a Go HTTP server.
## Requirements
`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
### Step 1: Build to `js/wasm`
In your Go code, replace [`http.ListenAndServe()`](https://pkg.go.dev/net/http#ListenAndServe) (or [`net.Listen()`](https://pkg.go.dev/net#Listen) + [`http.Serve()`](https://pkg.go.dev/net/http#Serve)) by [wasmhttp.Serve()](https://pkg.go.dev/github.com/nlepage/go-wasm-http-server#Serve):
📄 `server.go`
```go
//go:build !js && !wasm
package main
import (
"net/http"
)
func main() {
// Define handlers...
http.ListenAndServe(":8080", nil)
}
```
becomes:
📄 `server_js_wasm.go`
```go
//go:build js && wasm
package main
import (
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
)
func main() {
// Define handlers...
wasmhttp.Serve(nil)
}
```
You may want to use build tags as shown above (or file name suffixes) in order to be able to build both to WebAssembly and other targets.
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
// 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.1/sw.js')
registerWasmHTTPListener('path/to/server.wasm')
```
By default the server will deploy at the ServiceWorker's scope root, check [`registerWasmHTTPListener()`'s API](https://github.com/nlepage/go-wasm-http-server#registerwasmhttplistenerwasmurl-options) for more information.
You may want to add these additional event listeners in your ServiceWorker:
```js
// Skip installed stage and jump to activating stage
addEventListener('install', (event) => {
event.waitUntil(skipWaiting())
})
// Start controlling clients as soon as the SW is activated
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
```
### Step 3: Register the ServiceWorker
In your web page(s), register the ServiceWorker:
```html
<script>
// By default the ServiceWorker's scope will be "server/"
navigator.serviceWorker.register('server/sw.js')
</script>
```
Now your web page(s) may start fetching from the server:
```js
// The server will receive a request for "/path/to/resource"
fetch('server/path/to/resource').then(res => {
// use response...
})
```
## API
For Go API see [pkg.go.dev/github.com/nlepage/go-wasm-http-server](https://pkg.go.dev/github.com/nlepage/go-wasm-http-server#section-documentation)
### JavaScript API
### `registerWasmHTTPListener(wasmUrl, options)`
Instantiates and runs the WebAssembly module at `wasmUrl`, and registers a fetch listener forwarding requests to the WebAssembly module's server.
⚠ This function must be called only once in a ServiceWorker, if you want to register several servers you must use several ServiceWorkers.
The server will be "deployed" at the root of the ServiceWorker's scope by default, `base` may be used to deploy the server at a subpath of the scope.
See [ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register) for more information about the scope of a ServiceWorker.
#### `wasmUrl`
URL string of the WebAssembly module, example: `"path/to/my-module.wasm"`.
#### `options`
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.
## <abbr title="Frequently Asked Questions">FAQ</abbr> ❓
### Are WebSockets supported?
No, WebSockets arent and wont 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> <a href="https://github.com/nlepage/go-wasm-http-server/issues?q=author%3AEliCDavis" title="Bug reports">🐛</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 ## 🤝 Contributing
@@ -49,8 +235,8 @@ Give a ⭐️ if this project helped you!
## 📝 License ## 📝 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. This project is [Apache 2.0](https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE) licensed.
*** ***
_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_

View File

@@ -0,0 +1,27 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
)
var binaryName = ""
func main() {
http.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) {
res.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(res).Encode(map[string]string{
"message": fmt.Sprintf("Hello from %s at path %s", binaryName, os.Getenv("WASM_HTTP_PATH")),
}); err != nil {
panic(err)
}
})
wasmhttp.Serve(nil)
select {}
}

BIN
docs/hello-multiple/api1.wasm Executable file

Binary file not shown.

BIN
docs/hello-multiple/api2.wasm Executable file

Binary file not shown.

3
docs/hello-multiple/build.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
GOOS=js GOARCH=wasm go build -o api1.wasm --ldflags="-X 'main.binaryName=api1.wasm'" .
GOOS=js GOARCH=wasm go build -o api2.wasm --ldflags="-X 'main.binaryName=api2.wasm'" .

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>go-wasm-http-server hello demo</title>
<script>
navigator.serviceWorker.register('sw.js');
async function hello() {
const api = document.querySelector('#api').value;
const res = await fetch(`${api}/hello`);
const { message } = await res.json();
alert(message);
}
</script>
</head>
<body>
<label for="api">API: </label
><select id="api">
<option value="api1/1">api1.wasm first instance</option>
<option value="api1/2">api1.wasm second instance</option>
<option value="api2/1">api2.wasm first instance</option>
<option value="api2/2">api2.wasm second instance</option>
</select>
<button onclick="hello()">Hello</button>
</body>
</html>

15
docs/hello-multiple/sw.js Normal file
View File

@@ -0,0 +1,15 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js');
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js');
addEventListener('install', (event) => {
event.waitUntil(caches.open('examples').then((cache) => cache.addAll(['api1.wasm', 'api2.wasm'])));
});
addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});
registerWasmHTTPListener('api1.wasm', { base: 'api1/1/' });
registerWasmHTTPListener('api1.wasm', { base: 'api1/2/' });
registerWasmHTTPListener('api2.wasm', { base: 'api2/1/' });
registerWasmHTTPListener('api2.wasm', { base: 'api2/2/' });

34
docs/hello-sse/api.go Normal file
View 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

Binary file not shown.

56
docs/hello-sse/index.html Normal file
View File

@@ -0,0 +1,56 @@
<!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
View File

@@ -0,0 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js');
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/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' });

View File

@@ -1,32 +1,32 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>go-wasm-http-server hello with state demo</title> <title>go-wasm-http-server hello with state and keepalive demo</title>
<script> <script>
navigator.serviceWorker.register('sw.js').then(registration => { navigator.serviceWorker.register('sw.js').then((registration) => {
const sw = registration.active ?? registration.installing ?? registration.waiting const sw = registration.active ?? registration.installing ?? registration.waiting;
setInterval(() => sw.postMessage(null), 15000) setInterval(() => sw.postMessage(null), 15000);
}) });
async function hello() { async function hello() {
const name = document.querySelector("#name").value const name = document.querySelector('#name').value;
const res = await fetch('api/hello', { const res = await fetch('api/hello', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ name }) body: JSON.stringify({ name }),
}) });
const { message } = await res.json() const { message } = await res.json();
alert(message) alert(message);
} }
</script> </script>
</head> </head>
<body> <body>
<label for="name">Name: </label><input id="name" value="World"> <label for="name">Name: </label><input id="name" value="World" />
<button onclick="hello()">Hello</button> <button onclick="hello()">Hello</button>
</body> </body>
</html> </html>

View File

@@ -1,13 +1,16 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js') importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js');
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js');
addEventListener('install', event => { const wasm = '../hello-state/api.wasm';
event.waitUntil(skipWaiting())
})
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
addEventListener('message', () => {}) addEventListener('install', (event) => {
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)));
});
registerWasmHTTPListener('../hello-state/api.wasm', { base: 'api' }) addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});
addEventListener('message', () => {});
registerWasmHTTPListener(wasm, { base: 'api' });

View File

@@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"sync/atomic" "sync/atomic"
wasmhttp "github.com/nlepage/go-wasm-http-server" wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
) )
func main() { func main() {

Binary file not shown.

View File

@@ -3,27 +3,27 @@
<head> <head>
<title>go-wasm-http-server hello with state demo</title> <title>go-wasm-http-server hello with state demo</title>
<script> <script>
navigator.serviceWorker.register('sw.js') navigator.serviceWorker.register('sw.js');
async function hello() { async function hello() {
const name = document.querySelector("#name").value const name = document.querySelector('#name').value;
const res = await fetch('api/hello', { const res = await fetch('api/hello', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ name }) body: JSON.stringify({ name }),
}) });
const { message } = await res.json() const { message } = await res.json();
alert(message) alert(message);
} }
</script> </script>
</head> </head>
<body> <body>
<label for="name">Name: </label><input id="name" value="World"> <label for="name">Name: </label><input id="name" value="World" />
<button onclick="hello()">Hello</button> <button onclick="hello()">Hello</button>
</body> </body>
</html> </html>

View File

@@ -1,11 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js') importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js');
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js');
const wasm = 'api.wasm';
addEventListener('install', (event) => { addEventListener('install', (event) => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)));
}) });
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
registerWasmHTTPListener('api.wasm', { base: 'api' }) addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});
registerWasmHTTPListener(wasm, { base: 'api' });

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
wasmhttp "github.com/nlepage/go-wasm-http-server" wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
) )
func main() { func main() {

Binary file not shown.

View File

@@ -3,27 +3,27 @@
<head> <head>
<title>go-wasm-http-server hello demo</title> <title>go-wasm-http-server hello demo</title>
<script> <script>
navigator.serviceWorker.register('sw.js') navigator.serviceWorker.register('sw.js');
async function hello() { async function hello() {
const name = document.querySelector("#name").value const name = document.querySelector('#name').value;
const res = await fetch('api/hello', { const res = await fetch('api/hello', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ name }) body: JSON.stringify({ name }),
}) });
const { message } = await res.json() const { message } = await res.json();
alert(message) alert(message);
} }
</script> </script>
</head> </head>
<body> <body>
<label for="name">Name: </label><input id="name" value="World"> <label for="name">Name: </label><input id="name" value="World" />
<button onclick="hello()">Hello</button> <button onclick="hello()">Hello</button>
</body> </body>
</html> </html>

View File

@@ -1,11 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js') importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js');
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js');
const wasm = 'api.wasm';
addEventListener('install', (event) => { addEventListener('install', (event) => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)));
}) });
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
registerWasmHTTPListener('api.wasm', { base: 'api' }) addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});
registerWasmHTTPListener(wasm, { base: 'api' });

View File

@@ -1,14 +1,45 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<title>go-wasm-http-server demo</title> <title>go-wasm-http-server examples</title>
</head> </head>
<body> <body>
<h1>go-wasm-http-server examples</h1>
<ul> <ul>
<li><a href="hello">Hello demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello">sources</a>)</li> <li>
<li><a href="hello-state">Hello with state demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state">sources</a>)</li> <a href="hello/">Hello example</a> (<a
<li><a href="hello-state-keepalive">Hello with state and keepalive demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state-keepalive">sources</a>)</li> href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello"
<li><a href="https://nlepage.github.io/catption/wasm/">😺 Catption generator demo</a> (<a href="https://github.com/nlepage/catption/tree/wasm">sources</a>)</li> >Sources</a
>)
</li>
<li>
<a href="hello-multiple/">Hello example with multiple wasm instances</a> (<a
href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-multiple"
>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> </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> </body>
</html> </html>

42
docs/tinygo/README.md Normal file
View 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

Binary file not shown.

19
docs/tinygo/handlers.go Normal file
View 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
View 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
View 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
View 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.1/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
View 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 {}
}

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"net/http" "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. // 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{} // Wait for webpage event or use empty select{}
} }

10
go.mod
View File

@@ -1,3 +1,9 @@
module github.com/nlepage/go-wasm-http-server module github.com/nlepage/go-wasm-http-server/v2
go 1.13 go 1.22
require (
github.com/hack-pad/safejs v0.1.1
github.com/nlepage/go-js-promise v1.1.0
github.com/tmaxmax/go-sse v0.11.0
)

6
go.sum Normal file
View File

@@ -0,0 +1,6 @@
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.1.0 h1:BfvywsIMo4cpNOKyoReBWkxEW8f9HMwXqGc45wEKPRs=
github.com/nlepage/go-js-promise v1.1.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
github.com/tmaxmax/go-sse v0.11.0 h1:nogmJM6rJUoOLoAwEKeQe5XlVpt9l7N82SS1jI7lWFg=
github.com/tmaxmax/go-sse v0.11.0/go.mod h1:u/2kZQR1tyngo1lKaNCj1mJmhXGZWS1Zs5yiSOD+Eg8=

13
internal/jstype/types.go Normal file
View 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"))
)

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

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

View File

@@ -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. // Package wasmhttp 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 package wasmhttp

View File

@@ -5,5 +5,8 @@
"main": "index.js", "main": "index.js",
"repository": "https://github.com/nlepage/go-wasm-http-server", "repository": "https://github.com/nlepage/go-wasm-http-server",
"author": "Nicolas Lepage <19571875+nlepage@users.noreply.github.com>", "author": "Nicolas Lepage <19571875+nlepage@users.noreply.github.com>",
"license": "Apache-2.0" "license": "Apache-2.0",
"devDependencies": {
"all-contributors-cli": "^6.26.1"
}
} }

View File

@@ -1,55 +0,0 @@
package wasmhttp
import (
"syscall/js"
)
// NewPromise creates a new JavaScript Promise
func NewPromise() (p js.Value, resolve func(interface{}), reject func(interface{})) {
var cbFunc js.Func
cbFunc = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
cbFunc.Release()
resolve = func(value interface{}) {
args[0].Invoke(value)
}
reject = func(value interface{}) {
args[1].Invoke(value)
}
return js.Undefined()
})
p = js.Global().Get("Promise").New(cbFunc)
return
}
// Await waits for the Promise to be resolved and returns the value
func Await(p js.Value) (js.Value, error) {
resCh := make(chan js.Value)
var then js.Func
then = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
resCh <- args[0]
return nil
})
defer then.Release()
errCh := make(chan error)
var catch js.Func
catch = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
errCh <- js.Error{args[0]}
return nil
})
defer catch.Release()
p.Call("then", then).Call("catch", catch)
select {
case res := <-resCh:
return res, nil
case err := <-errCh:
return js.Undefined(), err
}
}

View File

@@ -1,33 +1,139 @@
package wasmhttp package wasmhttp
import ( import (
"bytes" "io"
"net/http" "net/http"
"net/http/httptest" "net/url"
"syscall/js" "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"
)
var (
navigator = safejs.Safe(js.Global().Get("navigator"))
) )
// Request builds and returns the equivalent http.Request // Request builds and returns the equivalent http.Request
func Request(r js.Value) *http.Request { func Request(uvalue js.Value) (*http.Request, error) {
jsBody := js.Global().Get("Uint8Array").New(Await(r.Call("arrayBuffer"))) value := safejs.Safe(uvalue)
body := make([]byte, jsBody.Get("length").Int())
js.CopyBytesToGo(body, jsBody)
req := httptest.NewRequest( method, err := value.GetString("method")
r.Get("method").String(), if err != nil {
r.Get("url").String(), return nil, err
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())
} }
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,
Host: u.Host,
Body: bodyReader,
Header: make(http.Header),
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
RequestURI: rawURL,
}
referer, err := value.GetString("referrer")
if err != nil {
return nil, err
}
req.Header.Set("Referer", referer)
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)
}
if req.UserAgent() == "" {
userAgent, err := navigator.GetString("userAgent")
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
}
return req, nil
} }

178
response.go Normal file
View File

@@ -0,0 +1,178 @@
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 !r.wroteHeader {
r.WriteHeader(200)
}
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))
}
}

View File

@@ -1,50 +0,0 @@
package wasmhttp
import (
"io/ioutil"
"net/http/httptest"
"syscall/js"
)
// ResponseRecorder extends httptest.ResponseRecorder and implements js.Wrapper
type ResponseRecorder struct {
*httptest.ResponseRecorder
}
// NewResponseRecorder returns a new ResponseRecorder
func NewResponseRecorder() ResponseRecorder {
return ResponseRecorder{httptest.NewRecorder()}
}
var _ js.Wrapper = ResponseRecorder{}
// JSValue builds and returns the equivalent JS Response (implementing js.Wrapper)
func (rr ResponseRecorder) JSValue() 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)
}

View File

@@ -1,55 +1,85 @@
package wasmhttp package wasmhttp
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strings" "strings"
"syscall/js" "syscall/js"
"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. // Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
func Serve(handler http.Handler) func() { func Serve(handler http.Handler) (func(), error) {
var h = handler h := handler
if h == nil { if h == nil {
h = http.DefaultServeMux h = http.DefaultServeMux
} }
var prefix = js.Global().Get("wasmhttp").Get("path").String() path := os.Getenv("WASM_HTTP_PATH")
prefix := path
for strings.HasSuffix(prefix, "/") { for strings.HasSuffix(prefix, "/") {
prefix = strings.TrimSuffix(prefix, "/") prefix = strings.TrimSuffix(prefix, "/")
} }
if prefix != "" { if prefix != "" {
var mux = http.NewServeMux() mux := http.NewServeMux()
mux.Handle(prefix+"/", http.StripPrefix(prefix, h)) mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
h = mux h = mux
} }
var cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} { handlerValue, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) interface{} {
var resPromise, resolve, reject = NewPromise() res, err := NewResponse()
if err != nil {
panic(err)
}
go func() { go func() {
ctx, cancel := context.WithCancel(res.Context())
defer func() { defer func() {
if r := recover(); r != nil { cancel()
if err, ok := r.(error); ok { }()
reject(fmt.Sprintf("wasmhttp: panic: %+v\n", err))
} else { defer func() {
reject(fmt.Sprintf("wasmhttp: panic: %v\n", r)) 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) 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", path, handlerValue); err != nil {
return nil, err
}
return cb.Release return handlerValue.Release, nil
} }

112
sw.js
View File

@@ -1,36 +1,94 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.15.7/misc/wasm/wasm_exec.js') class WasmHTTP {
#listeners = new Map();
function registerWasmHTTPListener(wasm, { base, args = [] } = {}) { setHandler(path, handler) {
let path = new URL(registration.scope).pathname const listener = this.#listeners.get(path);
if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}` if (!listener) {
throw new Error(`no listener for path "${path}"`);
const handlerPromise = new Promise(setHandler => {
self.wasmhttp = {
path,
setHandler,
} }
})
const go = new Go() listener.setHandler(handler);
go.argv = [wasm, ...args] }
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(({ instance }) => go.run(instance))
addEventListener('fetch', e => { handle(e) {
const { pathname } = new URL(e.request.url) const { pathname } = new URL(e.request.url);
if (!pathname.startsWith(path)) return
e.respondWith(handlerPromise.then(handler => handler(e.request))) for (const [path, listener] of this.#listeners) {
}) if (pathname.startsWith(path)) {
listener.handle(e);
return;
}
}
}
addListener({ wasm, base = '', cacheName, passthrough, args = [], env = {} }) {
const path = new URL(trimStart(base, '/'), registration.scope).pathname;
if (this.#listeners.has(path)) {
throw new Error(`a listener is already registered for path "${path}"`);
}
this.#listeners.set(path, new WasmHTTPListener({ wasm, path, cacheName, passthrough, args, env }));
}
}
class WasmHTTPListener {
#handlerPromise;
#resolveHandler;
#passthrough;
constructor({ wasm, path, cacheName, passthrough, args, env }) {
this.#handlerPromise = new Promise((resolve) => {
this.#resolveHandler = resolve;
});
this.#passthrough = passthrough;
this.#run({ wasm, path, cacheName, passthrough, args, env });
}
async #run({ wasm, path, cacheName, args, env }) {
try {
const go = new Go();
go.argv = [wasm, ...args];
go.env = { ...env, WASM_HTTP_PATH: path };
const cache = cacheName ? await caches.open(cacheName) : caches;
const source = (await cache.match(wasm)) ?? (await fetch(wasm));
const { instance } = await WebAssembly.instantiateStreaming(source, go.importObject);
await go.run(instance);
} catch (err) {
console.error(`error while running ${wasm} for path "${path}"`, err);
}
}
setHandler(handler) {
this.#resolveHandler(handler);
}
handle(e) {
if (this.#passthrough?.(e.request)) return;
// FIXME return 500 if run has thrown
e.respondWith(this.#handlerPromise.then((handler) => handler(e.request)));
}
}
self.wasmhttp = new WasmHTTP();
addEventListener('fetch', (e) => {
self.wasmhttp.handle(e);
});
function registerWasmHTTPListener(wasm, { base, cacheName, passthrough, args, env } = {}) {
self.wasmhttp.addListener({ wasm, base, cacheName, passthrough, args, env });
} }
function trimStart(s, c) { function trimStart(s, c) {
let r = s let r = s;
while (r.startsWith(c)) r = r.slice(c.length) while (r.startsWith(c)) r = r.slice(c.length);
return r return r;
}
function trimEnd(s, c) {
let r = s
while (r.endsWith(c)) r = r.slice(0, -c.length)
return r
} }