├── .all-contributorsrc ├── .github ├── CODEOWNERS ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── hello-sse │ ├── api.go │ ├── api.wasm │ ├── index.html │ └── sw.js ├── hello-state-keepalive │ ├── index.html │ └── sw.js ├── hello-state │ ├── api.go │ ├── api.wasm │ ├── index.html │ └── sw.js ├── hello │ ├── api.go │ ├── api.wasm │ ├── index.html │ └── sw.js ├── index.html └── tinygo │ ├── README.md │ ├── api.wasm │ ├── handlers.go │ ├── index.html │ ├── server.go │ ├── sw.js │ └── wasm.go ├── example_json_test.go ├── go.mod ├── go.sum ├── internal ├── jstype │ └── types.go ├── readablestream │ ├── reader.go │ └── writer.go └── safejs │ ├── bytes.go │ ├── func.go │ └── value.go ├── package-lock.json ├── package.go ├── package.json ├── request.go ├── response.go ├── serve.go └── sw.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "go-wasm-http-server", 3 | "projectOwner": "nlepage", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "commitConvention": "gitmoji", 12 | "contributors": [ 13 | { 14 | "login": "jphastings", 15 | "name": "JP Hastings-Edrei", 16 | "avatar_url": "https://avatars.githubusercontent.com/u/42999?v=4", 17 | "profile": "https://byjp.me/", 18 | "contributions": [ 19 | "code", 20 | "doc", 21 | "example" 22 | ] 23 | }, 24 | { 25 | "login": "EliCDavis", 26 | "name": "Eli Davis", 27 | "avatar_url": "https://avatars.githubusercontent.com/u/9094977?v=4", 28 | "profile": "https://recolude.com/", 29 | "contributions": [ 30 | "code", 31 | "bug" 32 | ] 33 | } 34 | ], 35 | "contributorsPerLine": 7, 36 | "linkToUsage": false 37 | } 38 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nlepage -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nlepage] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve go-wasm-http-server 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior or a link to an example repository. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment (please complete the following information):** 20 | - OS: [e.g. linux, darwin] 21 | - Arch: [e.g. amd64, arm64] 22 | - Browser: [e.g. chrome 78, safari 13] 23 | - Go Version: [e.g. 1.13.3] 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for go-wasm-http-server 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.wasm 3 | !docs/**/*.wasm 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.toolsEnvVars": { 3 | "GOOS": "js", 4 | "GOARCH": "wasm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at nicolas.lepage+go-wasm-http-server@zenika.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Nicolas Lepage 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to go-wasm-http-server 👋

2 |

3 | 4 | Go Reference 5 | 6 | 7 | License: Apache 2.0 8 | 9 |

10 | 11 | > Embed your Go HTTP handlers in a ServiceWorker (using [WebAssembly](https://mdn.io/WebAssembly/)) and emulate an HTTP server! 12 | 13 | ## Examples 14 | 15 | - [Hello example](https://nlepage.github.io/go-wasm-http-server/hello) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello)) 16 | - [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)) 17 | - [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)) 18 | - [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/)) 19 | - [😺 Catption generator example](https://nlepage.github.io/catption/wasm) ([sources](https://github.com/nlepage/catption/tree/wasm)) 20 | - [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)) 21 | - [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) 22 | 23 | ## How? 24 | 25 | Below is a talk given at the Go devroom of FOSDEM 2021 explaining how `go-wasm-http-server` works. 26 | 27 | > [!WARNING] 28 | > `go-wasm-http-server` has suffered major changes since this talk, be aware that it is not accurate anymore on several aspects. 29 | > Please refer to the documentation below for up to date usage of `go-wasm-http-server`. 30 | 31 | [![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) 32 | 33 | The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/). 34 | 35 | ## Why? 36 | 37 | `go-wasm-http-server` can help you put up a demonstration for a project without actually running a Go HTTP server. 38 | 39 | ## Requirements 40 | 41 | `go-wasm-http-server` requires you to build your Go application to WebAssembly, so you need to make sure your code is compatible: 42 | - no C bindings 43 | - no System dependencies such as file system or network (database server for example) 44 | - 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. 45 | 46 | ## Usage 47 | 48 | ### Step 1: Build to `js/wasm` 49 | 50 | 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): 51 | 52 | 📄 `server.go` 53 | ```go 54 | //go:build !js && !wasm 55 | 56 | package main 57 | 58 | import ( 59 | "net/http" 60 | ) 61 | 62 | func main() { 63 | // Define handlers... 64 | 65 | http.ListenAndServe(":8080", nil) 66 | } 67 | ``` 68 | 69 | becomes: 70 | 71 | 📄 `server_js_wasm.go` 72 | ```go 73 | //go:build js && wasm 74 | 75 | package main 76 | 77 | import ( 78 | wasmhttp "github.com/nlepage/go-wasm-http-server/v2" 79 | ) 80 | 81 | func main() { 82 | // Define handlers... 83 | 84 | wasmhttp.Serve(nil) 85 | } 86 | ``` 87 | 88 | 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. 89 | 90 | Then build your WebAssembly binary: 91 | 92 | ```sh 93 | # To compile with Go 94 | GOOS=js GOARCH=wasm go build -o server.wasm . 95 | 96 | # To compile with TinyGo, if your code is compatible 97 | GOOS=js GOARCH=wasm tinygo build -o server.wasm . 98 | ``` 99 | 100 | ### Step 2: Create ServiceWorker file 101 | 102 | First, check the version of Go/TinyGo you compiled your wasm with: 103 | 104 | ```sh 105 | $ go version 106 | go version go1.23.4 darwin/arm64 107 | # ^------^ 108 | 109 | $ tinygo version 110 | tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2) 111 | # ^----^ 112 | ``` 113 | 114 | Create a ServiceWorker file with the following code: 115 | 116 | 📄 `sw.js` 117 | ```js 118 | // Note the 'go.1.23.4' below, that matches the version you just found: 119 | importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.4/misc/wasm/wasm_exec.js') 120 | // If you compiled with TinyGo then, similarly, use: 121 | importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js') 122 | 123 | importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') 124 | 125 | registerWasmHTTPListener('path/to/server.wasm') 126 | ``` 127 | 128 | 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. 129 | 130 | You may want to add these additional event listeners in your ServiceWorker: 131 | 132 | ```js 133 | // Skip installed stage and jump to activating stage 134 | addEventListener('install', (event) => { 135 | event.waitUntil(skipWaiting()) 136 | }) 137 | 138 | // Start controlling clients as soon as the SW is activated 139 | addEventListener('activate', event => { 140 | event.waitUntil(clients.claim()) 141 | }) 142 | ``` 143 | 144 | ### Step 3: Register the ServiceWorker 145 | 146 | In your web page(s), register the ServiceWorker: 147 | 148 | ```html 149 | 153 | ``` 154 | 155 | Now your web page(s) may start fetching from the server: 156 | 157 | ```js 158 | // The server will receive a request for "/path/to/resource" 159 | fetch('server/path/to/resource').then(res => { 160 | // use response... 161 | }) 162 | ``` 163 | 164 | ## API 165 | 166 | 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) 167 | 168 | ### JavaScript API 169 | 170 | ### `registerWasmHTTPListener(wasmUrl, options)` 171 | 172 | Instantiates and runs the WebAssembly module at `wasmUrl`, and registers a fetch listener forwarding requests to the WebAssembly module's server. 173 | 174 | ⚠ This function must be called only once in a ServiceWorker, if you want to register several servers you must use several ServiceWorkers. 175 | 176 | 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. 177 | 178 | See [ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register) for more information about the scope of a ServiceWorker. 179 | 180 | #### `wasmUrl` 181 | 182 | URL string of the WebAssembly module, example: `"path/to/my-module.wasm"`. 183 | 184 | #### `options` 185 | 186 | An optional object containing: 187 | 188 | - `base` (`string`): Base path of the server, relative to the ServiceWorker's scope. 189 | - `cacheName` (`string`): Name of the [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to store the WebAssembly binary. 190 | - `args` (`string[]`): Arguments for the WebAssembly module. 191 | - `passthrough` (`(request: Request): boolean`): Optional callback to allow passing the request through to network. 192 | 193 | ## FAQ ❓ 194 | 195 | ### Are WebSockets supported? 196 | 197 | No, WebSockets aren’t and won’t be supported, because Service Workers cannot intercept websocket connections. 198 | 199 | 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/). 200 | 201 | ### Is it compatible with TinyGo? 202 | 203 | Yes, an example and some specific information is available [here](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/tinygo). 204 | 205 | ## Contributors ✨ 206 | 207 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 |
JP Hastings-Edrei
JP Hastings-Edrei

💻 📖 💡
Eli Davis
Eli Davis

💻 🐛
220 | 221 | 222 | 223 | 224 | 225 | 226 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 227 | 228 | ## 🤝 Contributing 229 | 230 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/nlepage/go-wasm-http-server/issues). 231 | 232 | ## Show your support 233 | 234 | Give a ⭐️ if this project helped you! 235 | 236 | ## 📝 License 237 | 238 | Copyright © 2025 [Nicolas Lepage](https://github.com/nlepage).
239 | This project is [Apache 2.0](https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE) licensed. 240 | 241 | *** 242 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 243 | -------------------------------------------------------------------------------- /docs/hello-sse/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/tmaxmax/go-sse" 8 | 9 | wasmhttp "github.com/nlepage/go-wasm-http-server/v2" 10 | ) 11 | 12 | func main() { 13 | s := &sse.Server{} 14 | t, _ := sse.NewType("ping") 15 | 16 | go func() { 17 | m := &sse.Message{ 18 | Type: t, 19 | } 20 | m.AppendData("Hello world") 21 | 22 | for range time.Tick(time.Second) { 23 | _ = s.Publish(m) 24 | } 25 | }() 26 | 27 | http.Handle("/events", s) 28 | 29 | if _, err := wasmhttp.Serve(nil); err != nil { 30 | panic(err) 31 | } 32 | 33 | select {} 34 | } 35 | -------------------------------------------------------------------------------- /docs/hello-sse/api.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nlepage/go-wasm-http-server/9ff6ec615afc121201d7b134842bfa1a9572b86b/docs/hello-sse/api.wasm -------------------------------------------------------------------------------- /docs/hello-sse/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server hello sse demo 5 | 48 | 49 | 50 |

51 | 52 | 53 | 54 |

55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/hello-sse/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 2 | importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') 3 | 4 | const wasm = 'api.wasm' 5 | 6 | addEventListener('install', (event) => { 7 | event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) 8 | }) 9 | 10 | addEventListener('activate', (event) => { 11 | event.waitUntil(clients.claim()) 12 | }) 13 | 14 | registerWasmHTTPListener(wasm, { base: 'api' }) 15 | -------------------------------------------------------------------------------- /docs/hello-state-keepalive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server hello with state and keepalive demo 5 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/hello-state-keepalive/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 2 | importScripts('../sw.js') 3 | 4 | const wasm = '../hello-state/api.wasm' 5 | 6 | addEventListener('install', event => { 7 | event.waitUntil(caches.open('hello-state').then((cache) => cache.add(wasm))) 8 | }) 9 | 10 | addEventListener('activate', event => { 11 | event.waitUntil(clients.claim()) 12 | }) 13 | 14 | addEventListener('message', () => {}) 15 | 16 | registerWasmHTTPListener(wasm, { base: 'api' }) 17 | -------------------------------------------------------------------------------- /docs/hello-state/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "sync/atomic" 8 | 9 | wasmhttp "github.com/nlepage/go-wasm-http-server/v2" 10 | ) 11 | 12 | func main() { 13 | var counter int32 14 | 15 | http.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) { 16 | params := make(map[string]string) 17 | if err := json.NewDecoder(req.Body).Decode(¶ms); err != nil { 18 | panic(err) 19 | } 20 | 21 | res.Header().Add("Content-Type", "application/json") 22 | if err := json.NewEncoder(res).Encode(map[string]string{ 23 | "message": fmt.Sprintf("Hello %s! (request %d)", params["name"], atomic.AddInt32(&counter, 1)), 24 | }); err != nil { 25 | panic(err) 26 | } 27 | }) 28 | 29 | wasmhttp.Serve(nil) 30 | 31 | select {} 32 | } 33 | -------------------------------------------------------------------------------- /docs/hello-state/api.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nlepage/go-wasm-http-server/9ff6ec615afc121201d7b134842bfa1a9572b86b/docs/hello-state/api.wasm -------------------------------------------------------------------------------- /docs/hello-state/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server hello with state demo 5 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/hello-state/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 2 | importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') 3 | 4 | const wasm = 'api.wasm' 5 | 6 | addEventListener('install', (event) => { 7 | event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) 8 | }) 9 | 10 | addEventListener('activate', (event) => { 11 | event.waitUntil(clients.claim()) 12 | }) 13 | 14 | registerWasmHTTPListener(wasm, { base: 'api' }) 15 | -------------------------------------------------------------------------------- /docs/hello/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | wasmhttp "github.com/nlepage/go-wasm-http-server/v2" 9 | ) 10 | 11 | func main() { 12 | http.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) { 13 | params := make(map[string]string) 14 | if err := json.NewDecoder(req.Body).Decode(¶ms); err != nil { 15 | panic(err) 16 | } 17 | 18 | res.Header().Add("Content-Type", "application/json") 19 | if err := json.NewEncoder(res).Encode(map[string]string{ 20 | "message": fmt.Sprintf("Hello %s!", params["name"]), 21 | }); err != nil { 22 | panic(err) 23 | } 24 | }) 25 | 26 | wasmhttp.Serve(nil) 27 | 28 | select {} 29 | } 30 | -------------------------------------------------------------------------------- /docs/hello/api.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nlepage/go-wasm-http-server/9ff6ec615afc121201d7b134842bfa1a9572b86b/docs/hello/api.wasm -------------------------------------------------------------------------------- /docs/hello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server hello demo 5 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/hello/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 2 | importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') 3 | 4 | const wasm = 'api.wasm' 5 | 6 | addEventListener('install', (event) => { 7 | event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) 8 | }) 9 | 10 | addEventListener('activate', (event) => { 11 | event.waitUntil(clients.claim()) 12 | }) 13 | 14 | registerWasmHTTPListener(wasm, { base: 'api' }) 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server examples 5 | 6 | 7 |

go-wasm-http-server examples

8 | 14 |

See README for more information.

15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/tinygo/README.md: -------------------------------------------------------------------------------- 1 | # Compiling with TinyGo 2 | 3 | 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/). 4 | 5 | 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). 6 | 7 | ## Prerequisites 8 | 9 | You'll need a version of [TinyGo installed](https://tinygo.org/getting-started/install/). (eg. `brew install tinygo-org/tools/tinygo`) 10 | 11 | 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). 12 | 13 | ## Build & run 14 | 15 | Compile the WASM blob with TinyGo (this has been done for you for this example): 16 | 17 | ```bash 18 | GOOS=js GOARCH=wasm tinygo build -o api.wasm . 19 | ``` 20 | 21 | Run the server (with Go, not TinyGo): 22 | 23 | ```bash 24 | $ go run . 25 | Server starting on http://127.0.0.1: 26 | ``` 27 | 28 | ## Important notes 29 | 30 | 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: 31 | 32 | ```js 33 | importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js') 34 | ``` 35 | 36 | Note that the `0.35.0` within the path matches the TinyGo version used: 37 | 38 | ```sh 39 | $ tinygo version 40 | tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2) 41 | # ^----^ 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/tinygo/api.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nlepage/go-wasm-http-server/9ff6ec615afc121201d7b134842bfa1a9572b86b/docs/tinygo/api.wasm -------------------------------------------------------------------------------- /docs/tinygo/handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "runtime" 7 | ) 8 | 9 | func goRuntimeHandler(res http.ResponseWriter, req *http.Request) { 10 | res.Header().Add("Content-Type", "application/json") 11 | if err := json.NewEncoder(res).Encode(map[string]string{ 12 | "os": runtime.GOOS, 13 | "arch": runtime.GOARCH, 14 | "compiler": runtime.Compiler, 15 | "version": runtime.Version(), 16 | }); err != nil { 17 | panic(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/tinygo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | go-wasm-http-server tinygo demo 5 | 30 | 31 | 32 |

This example demonstrates that go-wasm-http-server can be compiled with TinyGo, producing significantly smaller WASM blobs, at the expense of at least one known bug, and a reduced standard library.

33 |
WASM HTTP Service Worker:
☁️ Not loaded — will call server
34 | 35 |
    36 |
  1. 37 |
  2. 38 |
  3. Call the API again (Step 1)
  4. 39 |
40 | 41 |

Response:

42 |

43 |   
44 | 
45 | 


--------------------------------------------------------------------------------
/docs/tinygo/server.go:
--------------------------------------------------------------------------------
 1 | //go:build !wasm
 2 | // +build !wasm
 3 | 
 4 | package main
 5 | 
 6 | import (
 7 | 	"embed"
 8 | 	"fmt"
 9 | 	"log"
10 | 	"net"
11 | 	"net/http"
12 | )
13 | 
14 | //go:embed *.html *.js *.wasm
15 | var thisDir embed.FS
16 | 
17 | func main() {
18 | 	// Serve all files in this directory statically
19 | 	http.Handle("/", http.FileServer(http.FS(thisDir)))
20 | 
21 | 	// Note that this needs to be mounted at /api/tiny, rather than just /tiny (like in wasm.go)
22 | 	// because the service worker mounts the WASM server at /api (at the end of sw.js)
23 | 	http.HandleFunc("/api/tiny", goRuntimeHandler)
24 | 
25 | 	// 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)
26 | 	listener, err := net.Listen("tcp", ":0")
27 | 	if err != nil {
28 | 		log.Fatalf("Unable to claim a port to start server on: %v", err)
29 | 	}
30 | 
31 | 	// Share the port being used & start
32 | 	fmt.Printf("Server starting on http://127.0.0.1:%d\n", listener.Addr().(*net.TCPAddr).Port)
33 | 	panic(http.Serve(listener, nil))
34 | }
35 | 


--------------------------------------------------------------------------------
/docs/tinygo/sw.js:
--------------------------------------------------------------------------------
 1 | importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
 2 | importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js')
 3 | 
 4 | const wasm = 'api.wasm'
 5 | 
 6 | addEventListener('install', (event) => {
 7 |   event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
 8 | })
 9 | 
10 | addEventListener('activate', (event) => {
11 |   event.waitUntil(clients.claim())
12 | })
13 | 
14 | registerWasmHTTPListener(wasm, { base: 'api' })
15 | 


--------------------------------------------------------------------------------
/docs/tinygo/wasm.go:
--------------------------------------------------------------------------------
 1 | //go:build wasm
 2 | // +build wasm
 3 | 
 4 | package main
 5 | 
 6 | import (
 7 | 	"net/http"
 8 | 
 9 | 	wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
10 | )
11 | 
12 | func main() {
13 | 	http.HandleFunc("/tiny", goRuntimeHandler)
14 | 
15 | 	wasmhttp.Serve(nil)
16 | 
17 | 	select {}
18 | }
19 | 


--------------------------------------------------------------------------------
/example_json_test.go:
--------------------------------------------------------------------------------
 1 | package wasmhttp_test
 2 | 
 3 | import (
 4 | 	"encoding/json"
 5 | 	"fmt"
 6 | 	"net/http"
 7 | 
 8 | 	wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
 9 | )
10 | 
11 | // Demonstrates a simple hello JSON service.
12 | func Example_json() {
13 | 	http.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) {
14 | 		params := make(map[string]string)
15 | 		if err := json.NewDecoder(req.Body).Decode(¶ms); err != nil {
16 | 			panic(err)
17 | 		}
18 | 
19 | 		if err := json.NewEncoder(res).Encode(map[string]string{
20 | 			"message": fmt.Sprintf("Hello %s!", params["name"]),
21 | 		}); err != nil {
22 | 			panic(err)
23 | 		}
24 | 	})
25 | 
26 | 	release, err := wasmhttp.Serve(nil)
27 | 	if err != nil {
28 | 		panic(err)
29 | 	}
30 | 	defer release()
31 | 
32 | 	// Wait for webpage event or use empty select{}
33 | }
34 | 


--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
 1 | module github.com/nlepage/go-wasm-http-server/v2
 2 | 
 3 | go 1.18
 4 | 
 5 | require (
 6 | 	github.com/hack-pad/safejs v0.1.1
 7 | 	github.com/nlepage/go-js-promise v1.0.0
 8 | 	github.com/tmaxmax/go-sse v0.8.0
 9 | )
10 | 


--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
 1 | github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
 2 | github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
 3 | github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ=
 4 | github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
 5 | github.com/tmaxmax/go-sse v0.8.0 h1:pPpTgyyi1r7vG2o6icebnpGEh3ebcnBXqDWkb7aTofs=
 6 | github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM=
 7 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 8 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 9 | golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
10 | 


--------------------------------------------------------------------------------
/internal/jstype/types.go:
--------------------------------------------------------------------------------
 1 | package jstype
 2 | 
 3 | import (
 4 | 	"syscall/js"
 5 | 
 6 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
 7 | )
 8 | 
 9 | var (
10 | 	ReadableStream = safejs.Safe(js.Global().Get("ReadableStream"))
11 | 	Response       = safejs.Safe(js.Global().Get("Response"))
12 | 	Uint8Array     = safejs.Safe(js.Global().Get("Uint8Array"))
13 | )
14 | 


--------------------------------------------------------------------------------
/internal/readablestream/reader.go:
--------------------------------------------------------------------------------
 1 | package readablestream
 2 | 
 3 | import (
 4 | 	"io"
 5 | 
 6 | 	promise "github.com/nlepage/go-js-promise"
 7 | 
 8 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
 9 | )
10 | 
11 | type Reader struct {
12 | 	value safejs.Value
13 | 	buf   []byte
14 | 	off   int
15 | }
16 | 
17 | var _ io.ReadCloser = (*Reader)(nil)
18 | 
19 | func NewReader(r safejs.Value) *Reader {
20 | 	return &Reader{
21 | 		value: r,
22 | 	}
23 | }
24 | 
25 | func (r *Reader) Read(p []byte) (int, error) {
26 | 	if r.off < len(r.buf) {
27 | 		n := copy(p, r.buf[r.off:])
28 | 
29 | 		r.off += n
30 | 
31 | 		return n, nil
32 | 	}
33 | 
34 | 	r.off = 0
35 | 
36 | 	pRes, err := r.value.Call("read")
37 | 	if err != nil {
38 | 		return 0, err
39 | 	}
40 | 
41 | 	ures, err := promise.Await(safejs.Unsafe(pRes))
42 | 	if err != nil {
43 | 		return 0, err
44 | 	}
45 | 
46 | 	res := safejs.Safe(ures)
47 | 
48 | 	done, err := res.GetBool("done")
49 | 	if err != nil {
50 | 		return 0, err
51 | 	}
52 | 	if done {
53 | 		return 0, io.EOF
54 | 	}
55 | 
56 | 	value, err := res.Get("value")
57 | 	if err != nil {
58 | 		return 0, err
59 | 	}
60 | 
61 | 	l, err := value.GetInt("length")
62 | 	if err != nil {
63 | 		return 0, err
64 | 	}
65 | 
66 | 	if cap(r.buf) < l {
67 | 		r.buf = make([]byte, l)
68 | 	}
69 | 	if len(r.buf) < cap(r.buf) {
70 | 		r.buf = r.buf[:cap(r.buf)]
71 | 	}
72 | 
73 | 	n, err := safejs.CopyBytesToGo(r.buf, value)
74 | 	if err != nil {
75 | 		return 0, err
76 | 	}
77 | 
78 | 	r.buf = r.buf[:n]
79 | 
80 | 	n = copy(p, r.buf[r.off:])
81 | 
82 | 	r.off += n
83 | 
84 | 	return n, nil
85 | }
86 | 
87 | func (r *Reader) Close() error {
88 | 	p, err := r.value.Call("cancel")
89 | 	if err != nil {
90 | 		return err
91 | 	}
92 | 
93 | 	_, err = promise.Await(safejs.Unsafe(p))
94 | 
95 | 	return err
96 | }
97 | 


--------------------------------------------------------------------------------
/internal/readablestream/writer.go:
--------------------------------------------------------------------------------
  1 | package readablestream
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"io"
  6 | 
  7 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/jstype"
  8 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
  9 | )
 10 | 
 11 | type Writer struct {
 12 | 	Value      safejs.Value
 13 | 	controller safejs.Value
 14 | 	ctx        context.Context
 15 | 	cancelled  bool
 16 | }
 17 | 
 18 | var _ io.WriteCloser = (*Writer)(nil)
 19 | 
 20 | func NewWriter() (*Writer, error) {
 21 | 	var rs *Writer
 22 | 
 23 | 	var start safejs.Func
 24 | 	var controller safejs.Value
 25 | 
 26 | 	start, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any {
 27 | 		defer start.Release()
 28 | 		controller = args[0]
 29 | 		return nil
 30 | 	})
 31 | 	if err != nil {
 32 | 		return nil, err
 33 | 	}
 34 | 
 35 | 	var cancel safejs.Func
 36 | 	ctx, cancelCtx := context.WithCancel(context.Background())
 37 | 
 38 | 	cancel, err = safejs.FuncOf(func(_ safejs.Value, _ []safejs.Value) any {
 39 | 		defer cancel.Release()
 40 | 		rs.cancelled = true
 41 | 		cancelCtx()
 42 | 		return nil
 43 | 	})
 44 | 	if err != nil {
 45 | 		return nil, err
 46 | 	}
 47 | 
 48 | 	source, err := safejs.ValueOf(map[string]any{
 49 | 		"start":  safejs.Unsafe(start.Value()),
 50 | 		"cancel": safejs.Unsafe(cancel.Value()),
 51 | 	})
 52 | 	if err != nil {
 53 | 		return nil, err
 54 | 	}
 55 | 
 56 | 	value, err := jstype.ReadableStream.New(source)
 57 | 	if err != nil {
 58 | 		return nil, err
 59 | 	}
 60 | 
 61 | 	rs = &Writer{
 62 | 		Value:      value,
 63 | 		controller: controller,
 64 | 		ctx:        ctx,
 65 | 	}
 66 | 
 67 | 	return rs, nil
 68 | }
 69 | 
 70 | func (rs *Writer) Write(b []byte) (int, error) {
 71 | 	if rs.cancelled {
 72 | 		return 0, nil
 73 | 	}
 74 | 
 75 | 	chunk, err := jstype.Uint8Array.New(len(b)) // FIXME reuse same Uint8Array?
 76 | 	if err != nil {
 77 | 		return 0, err
 78 | 	}
 79 | 
 80 | 	n, err := safejs.CopyBytesToJS(chunk, b)
 81 | 	if err != nil {
 82 | 		return 0, err
 83 | 	}
 84 | 
 85 | 	_, err = rs.controller.Call("enqueue", chunk)
 86 | 
 87 | 	return n, err
 88 | }
 89 | 
 90 | func (rs *Writer) Close() error {
 91 | 	if rs.cancelled {
 92 | 		return nil
 93 | 	}
 94 | 
 95 | 	_, err := rs.controller.Call("close")
 96 | 	return err
 97 | }
 98 | 
 99 | func (rs *Writer) Context() context.Context {
100 | 	return rs.ctx
101 | }
102 | 


--------------------------------------------------------------------------------
/internal/safejs/bytes.go:
--------------------------------------------------------------------------------
 1 | package safejs
 2 | 
 3 | import "github.com/hack-pad/safejs"
 4 | 
 5 | func CopyBytesToGo(dst []byte, src Value) (int, error) {
 6 | 	return safejs.CopyBytesToGo(dst, safejs.Value(src))
 7 | }
 8 | 
 9 | func CopyBytesToJS(dst Value, src []byte) (int, error) {
10 | 	return safejs.CopyBytesToJS(safejs.Value(dst), src)
11 | }
12 | 


--------------------------------------------------------------------------------
/internal/safejs/func.go:
--------------------------------------------------------------------------------
 1 | package safejs
 2 | 
 3 | import (
 4 | 	"github.com/hack-pad/safejs"
 5 | )
 6 | 
 7 | type Func safejs.Func
 8 | 
 9 | func FuncOf(fn func(this Value, args []Value) any) (Func, error) {
10 | 	r, err := safejs.FuncOf(func(this safejs.Value, args []safejs.Value) any {
11 | 		args2 := make([]Value, len(args))
12 | 		for i, v := range args {
13 | 			args2[i] = Value(v)
14 | 		}
15 | 		return fn(Value(this), []Value(args2))
16 | 	})
17 | 	return Func(r), err
18 | }
19 | 
20 | func (f Func) Release() {
21 | 	safejs.Func(f).Release()
22 | }
23 | 
24 | func (f Func) Value() Value {
25 | 	return Value(safejs.Func(f).Value())
26 | }
27 | 


--------------------------------------------------------------------------------
/internal/safejs/value.go:
--------------------------------------------------------------------------------
  1 | package safejs
  2 | 
  3 | import (
  4 | 	"syscall/js"
  5 | 
  6 | 	"github.com/hack-pad/safejs"
  7 | )
  8 | 
  9 | type Value safejs.Value
 10 | 
 11 | func Safe(v js.Value) Value {
 12 | 	return Value(safejs.Safe(v))
 13 | }
 14 | 
 15 | func Unsafe(v Value) js.Value {
 16 | 	return safejs.Unsafe(safejs.Value(v))
 17 | }
 18 | 
 19 | func ValueOf(value any) (Value, error) {
 20 | 	v, err := safejs.ValueOf(value)
 21 | 	return Value(v), err
 22 | }
 23 | 
 24 | func (v Value) Call(m string, args ...any) (Value, error) {
 25 | 	args = toJSValue(args).([]any)
 26 | 	r, err := safejs.Value(v).Call(m, args...)
 27 | 	return Value(r), err
 28 | }
 29 | 
 30 | func (v Value) Get(p string) (Value, error) {
 31 | 	r, err := safejs.Value(v).Get(p)
 32 | 	return Value(r), err
 33 | }
 34 | 
 35 | func (v Value) GetBool(p string) (bool, error) {
 36 | 	bv, err := v.Get(p)
 37 | 	if err != nil {
 38 | 		return false, err
 39 | 	}
 40 | 
 41 | 	return safejs.Value(bv).Bool()
 42 | }
 43 | 
 44 | func (v Value) GetInt(p string) (int, error) {
 45 | 	iv, err := v.Get(p)
 46 | 	if err != nil {
 47 | 		return 0, err
 48 | 	}
 49 | 
 50 | 	return safejs.Value(iv).Int()
 51 | }
 52 | 
 53 | func (v Value) GetString(p string) (string, error) {
 54 | 	sv, err := v.Get(p)
 55 | 	if err != nil {
 56 | 		return "", err
 57 | 	}
 58 | 
 59 | 	return safejs.Value(sv).String()
 60 | }
 61 | 
 62 | func (v Value) Index(i int) (Value, error) {
 63 | 	r, err := safejs.Value(v).Index(i)
 64 | 	return Value(r), err
 65 | }
 66 | 
 67 | func (v Value) IndexString(i int) (string, error) {
 68 | 	sv, err := v.Index(i)
 69 | 	if err != nil {
 70 | 		return "", err
 71 | 	}
 72 | 
 73 | 	return safejs.Value(sv).String()
 74 | }
 75 | 
 76 | func (v Value) IsNull() bool {
 77 | 	return safejs.Value(v).IsNull()
 78 | }
 79 | 
 80 | func (v Value) IsUndefined() bool {
 81 | 	return safejs.Value(v).IsUndefined()
 82 | }
 83 | 
 84 | func (v Value) New(args ...any) (Value, error) {
 85 | 	args = toJSValue(args).([]any)
 86 | 	r, err := safejs.Value(v).New(args...)
 87 | 	return Value(r), err
 88 | }
 89 | 
 90 | func toJSValue(jsValue any) any {
 91 | 	switch value := jsValue.(type) {
 92 | 	case Value:
 93 | 		return safejs.Value(value)
 94 | 	case Func:
 95 | 		return safejs.Func(value)
 96 | 	case map[string]any:
 97 | 		newValue := make(map[string]any)
 98 | 		for mapKey, mapValue := range value {
 99 | 			newValue[mapKey] = toJSValue(mapValue)
100 | 		}
101 | 		return newValue
102 | 	case []any:
103 | 		newValue := make([]any, len(value))
104 | 		for i, arg := range value {
105 | 			newValue[i] = toJSValue(arg)
106 | 		}
107 | 		return newValue
108 | 	default:
109 | 		return jsValue
110 | 	}
111 | }
112 | 


--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "name": "go-wasm-http-server",
  3 |   "version": "0.1.0",
  4 |   "lockfileVersion": 3,
  5 |   "requires": true,
  6 |   "packages": {
  7 |     "": {
  8 |       "name": "go-wasm-http-server",
  9 |       "version": "0.1.0",
 10 |       "license": "Apache-2.0",
 11 |       "devDependencies": {
 12 |         "all-contributors-cli": "^6.26.1"
 13 |       }
 14 |     },
 15 |     "node_modules/@babel/runtime": {
 16 |       "version": "7.26.0",
 17 |       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
 18 |       "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
 19 |       "dev": true,
 20 |       "license": "MIT",
 21 |       "dependencies": {
 22 |         "regenerator-runtime": "^0.14.0"
 23 |       },
 24 |       "engines": {
 25 |         "node": ">=6.9.0"
 26 |       }
 27 |     },
 28 |     "node_modules/all-contributors-cli": {
 29 |       "version": "6.26.1",
 30 |       "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz",
 31 |       "integrity": "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==",
 32 |       "dev": true,
 33 |       "license": "MIT",
 34 |       "dependencies": {
 35 |         "@babel/runtime": "^7.7.6",
 36 |         "async": "^3.1.0",
 37 |         "chalk": "^4.0.0",
 38 |         "didyoumean": "^1.2.1",
 39 |         "inquirer": "^7.3.3",
 40 |         "json-fixer": "^1.6.8",
 41 |         "lodash": "^4.11.2",
 42 |         "node-fetch": "^2.6.0",
 43 |         "pify": "^5.0.0",
 44 |         "yargs": "^15.0.1"
 45 |       },
 46 |       "bin": {
 47 |         "all-contributors": "dist/cli.js"
 48 |       },
 49 |       "engines": {
 50 |         "node": ">=4"
 51 |       },
 52 |       "optionalDependencies": {
 53 |         "prettier": "^2"
 54 |       }
 55 |     },
 56 |     "node_modules/ansi-escapes": {
 57 |       "version": "4.3.2",
 58 |       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
 59 |       "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
 60 |       "dev": true,
 61 |       "license": "MIT",
 62 |       "dependencies": {
 63 |         "type-fest": "^0.21.3"
 64 |       },
 65 |       "engines": {
 66 |         "node": ">=8"
 67 |       },
 68 |       "funding": {
 69 |         "url": "https://github.com/sponsors/sindresorhus"
 70 |       }
 71 |     },
 72 |     "node_modules/ansi-regex": {
 73 |       "version": "5.0.1",
 74 |       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 75 |       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 76 |       "dev": true,
 77 |       "license": "MIT",
 78 |       "engines": {
 79 |         "node": ">=8"
 80 |       }
 81 |     },
 82 |     "node_modules/ansi-styles": {
 83 |       "version": "4.3.0",
 84 |       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 85 |       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
 86 |       "dev": true,
 87 |       "license": "MIT",
 88 |       "dependencies": {
 89 |         "color-convert": "^2.0.1"
 90 |       },
 91 |       "engines": {
 92 |         "node": ">=8"
 93 |       },
 94 |       "funding": {
 95 |         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 96 |       }
 97 |     },
 98 |     "node_modules/async": {
 99 |       "version": "3.2.6",
100 |       "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
101 |       "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
102 |       "dev": true,
103 |       "license": "MIT"
104 |     },
105 |     "node_modules/camelcase": {
106 |       "version": "5.3.1",
107 |       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
108 |       "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
109 |       "dev": true,
110 |       "license": "MIT",
111 |       "engines": {
112 |         "node": ">=6"
113 |       }
114 |     },
115 |     "node_modules/chalk": {
116 |       "version": "4.1.2",
117 |       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
118 |       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
119 |       "dev": true,
120 |       "license": "MIT",
121 |       "dependencies": {
122 |         "ansi-styles": "^4.1.0",
123 |         "supports-color": "^7.1.0"
124 |       },
125 |       "engines": {
126 |         "node": ">=10"
127 |       },
128 |       "funding": {
129 |         "url": "https://github.com/chalk/chalk?sponsor=1"
130 |       }
131 |     },
132 |     "node_modules/chardet": {
133 |       "version": "0.7.0",
134 |       "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
135 |       "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
136 |       "dev": true,
137 |       "license": "MIT"
138 |     },
139 |     "node_modules/cli-cursor": {
140 |       "version": "3.1.0",
141 |       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
142 |       "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
143 |       "dev": true,
144 |       "license": "MIT",
145 |       "dependencies": {
146 |         "restore-cursor": "^3.1.0"
147 |       },
148 |       "engines": {
149 |         "node": ">=8"
150 |       }
151 |     },
152 |     "node_modules/cli-width": {
153 |       "version": "3.0.0",
154 |       "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
155 |       "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
156 |       "dev": true,
157 |       "license": "ISC",
158 |       "engines": {
159 |         "node": ">= 10"
160 |       }
161 |     },
162 |     "node_modules/cliui": {
163 |       "version": "6.0.0",
164 |       "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
165 |       "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
166 |       "dev": true,
167 |       "license": "ISC",
168 |       "dependencies": {
169 |         "string-width": "^4.2.0",
170 |         "strip-ansi": "^6.0.0",
171 |         "wrap-ansi": "^6.2.0"
172 |       }
173 |     },
174 |     "node_modules/color-convert": {
175 |       "version": "2.0.1",
176 |       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
177 |       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
178 |       "dev": true,
179 |       "license": "MIT",
180 |       "dependencies": {
181 |         "color-name": "~1.1.4"
182 |       },
183 |       "engines": {
184 |         "node": ">=7.0.0"
185 |       }
186 |     },
187 |     "node_modules/color-name": {
188 |       "version": "1.1.4",
189 |       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
190 |       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
191 |       "dev": true,
192 |       "license": "MIT"
193 |     },
194 |     "node_modules/decamelize": {
195 |       "version": "1.2.0",
196 |       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
197 |       "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
198 |       "dev": true,
199 |       "license": "MIT",
200 |       "engines": {
201 |         "node": ">=0.10.0"
202 |       }
203 |     },
204 |     "node_modules/didyoumean": {
205 |       "version": "1.2.2",
206 |       "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
207 |       "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
208 |       "dev": true,
209 |       "license": "Apache-2.0"
210 |     },
211 |     "node_modules/emoji-regex": {
212 |       "version": "8.0.0",
213 |       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
214 |       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
215 |       "dev": true,
216 |       "license": "MIT"
217 |     },
218 |     "node_modules/escape-string-regexp": {
219 |       "version": "1.0.5",
220 |       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
221 |       "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
222 |       "dev": true,
223 |       "license": "MIT",
224 |       "engines": {
225 |         "node": ">=0.8.0"
226 |       }
227 |     },
228 |     "node_modules/external-editor": {
229 |       "version": "3.1.0",
230 |       "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
231 |       "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
232 |       "dev": true,
233 |       "license": "MIT",
234 |       "dependencies": {
235 |         "chardet": "^0.7.0",
236 |         "iconv-lite": "^0.4.24",
237 |         "tmp": "^0.0.33"
238 |       },
239 |       "engines": {
240 |         "node": ">=4"
241 |       }
242 |     },
243 |     "node_modules/figures": {
244 |       "version": "3.2.0",
245 |       "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
246 |       "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
247 |       "dev": true,
248 |       "license": "MIT",
249 |       "dependencies": {
250 |         "escape-string-regexp": "^1.0.5"
251 |       },
252 |       "engines": {
253 |         "node": ">=8"
254 |       },
255 |       "funding": {
256 |         "url": "https://github.com/sponsors/sindresorhus"
257 |       }
258 |     },
259 |     "node_modules/find-up": {
260 |       "version": "4.1.0",
261 |       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
262 |       "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
263 |       "dev": true,
264 |       "license": "MIT",
265 |       "dependencies": {
266 |         "locate-path": "^5.0.0",
267 |         "path-exists": "^4.0.0"
268 |       },
269 |       "engines": {
270 |         "node": ">=8"
271 |       }
272 |     },
273 |     "node_modules/get-caller-file": {
274 |       "version": "2.0.5",
275 |       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
276 |       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
277 |       "dev": true,
278 |       "license": "ISC",
279 |       "engines": {
280 |         "node": "6.* || 8.* || >= 10.*"
281 |       }
282 |     },
283 |     "node_modules/has-flag": {
284 |       "version": "4.0.0",
285 |       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
286 |       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
287 |       "dev": true,
288 |       "license": "MIT",
289 |       "engines": {
290 |         "node": ">=8"
291 |       }
292 |     },
293 |     "node_modules/iconv-lite": {
294 |       "version": "0.4.24",
295 |       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
296 |       "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
297 |       "dev": true,
298 |       "license": "MIT",
299 |       "dependencies": {
300 |         "safer-buffer": ">= 2.1.2 < 3"
301 |       },
302 |       "engines": {
303 |         "node": ">=0.10.0"
304 |       }
305 |     },
306 |     "node_modules/inquirer": {
307 |       "version": "7.3.3",
308 |       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
309 |       "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
310 |       "dev": true,
311 |       "license": "MIT",
312 |       "dependencies": {
313 |         "ansi-escapes": "^4.2.1",
314 |         "chalk": "^4.1.0",
315 |         "cli-cursor": "^3.1.0",
316 |         "cli-width": "^3.0.0",
317 |         "external-editor": "^3.0.3",
318 |         "figures": "^3.0.0",
319 |         "lodash": "^4.17.19",
320 |         "mute-stream": "0.0.8",
321 |         "run-async": "^2.4.0",
322 |         "rxjs": "^6.6.0",
323 |         "string-width": "^4.1.0",
324 |         "strip-ansi": "^6.0.0",
325 |         "through": "^2.3.6"
326 |       },
327 |       "engines": {
328 |         "node": ">=8.0.0"
329 |       }
330 |     },
331 |     "node_modules/is-fullwidth-code-point": {
332 |       "version": "3.0.0",
333 |       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
334 |       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
335 |       "dev": true,
336 |       "license": "MIT",
337 |       "engines": {
338 |         "node": ">=8"
339 |       }
340 |     },
341 |     "node_modules/json-fixer": {
342 |       "version": "1.6.15",
343 |       "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.15.tgz",
344 |       "integrity": "sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==",
345 |       "dev": true,
346 |       "license": "MIT",
347 |       "dependencies": {
348 |         "@babel/runtime": "^7.18.9",
349 |         "chalk": "^4.1.2",
350 |         "pegjs": "^0.10.0"
351 |       },
352 |       "engines": {
353 |         "node": ">=10"
354 |       }
355 |     },
356 |     "node_modules/locate-path": {
357 |       "version": "5.0.0",
358 |       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
359 |       "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
360 |       "dev": true,
361 |       "license": "MIT",
362 |       "dependencies": {
363 |         "p-locate": "^4.1.0"
364 |       },
365 |       "engines": {
366 |         "node": ">=8"
367 |       }
368 |     },
369 |     "node_modules/lodash": {
370 |       "version": "4.17.21",
371 |       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
372 |       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
373 |       "dev": true,
374 |       "license": "MIT"
375 |     },
376 |     "node_modules/mimic-fn": {
377 |       "version": "2.1.0",
378 |       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
379 |       "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
380 |       "dev": true,
381 |       "license": "MIT",
382 |       "engines": {
383 |         "node": ">=6"
384 |       }
385 |     },
386 |     "node_modules/mute-stream": {
387 |       "version": "0.0.8",
388 |       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
389 |       "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
390 |       "dev": true,
391 |       "license": "ISC"
392 |     },
393 |     "node_modules/node-fetch": {
394 |       "version": "2.7.0",
395 |       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
396 |       "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
397 |       "dev": true,
398 |       "license": "MIT",
399 |       "dependencies": {
400 |         "whatwg-url": "^5.0.0"
401 |       },
402 |       "engines": {
403 |         "node": "4.x || >=6.0.0"
404 |       },
405 |       "peerDependencies": {
406 |         "encoding": "^0.1.0"
407 |       },
408 |       "peerDependenciesMeta": {
409 |         "encoding": {
410 |           "optional": true
411 |         }
412 |       }
413 |     },
414 |     "node_modules/onetime": {
415 |       "version": "5.1.2",
416 |       "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
417 |       "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
418 |       "dev": true,
419 |       "license": "MIT",
420 |       "dependencies": {
421 |         "mimic-fn": "^2.1.0"
422 |       },
423 |       "engines": {
424 |         "node": ">=6"
425 |       },
426 |       "funding": {
427 |         "url": "https://github.com/sponsors/sindresorhus"
428 |       }
429 |     },
430 |     "node_modules/os-tmpdir": {
431 |       "version": "1.0.2",
432 |       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
433 |       "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
434 |       "dev": true,
435 |       "license": "MIT",
436 |       "engines": {
437 |         "node": ">=0.10.0"
438 |       }
439 |     },
440 |     "node_modules/p-limit": {
441 |       "version": "2.3.0",
442 |       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
443 |       "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
444 |       "dev": true,
445 |       "license": "MIT",
446 |       "dependencies": {
447 |         "p-try": "^2.0.0"
448 |       },
449 |       "engines": {
450 |         "node": ">=6"
451 |       },
452 |       "funding": {
453 |         "url": "https://github.com/sponsors/sindresorhus"
454 |       }
455 |     },
456 |     "node_modules/p-locate": {
457 |       "version": "4.1.0",
458 |       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
459 |       "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
460 |       "dev": true,
461 |       "license": "MIT",
462 |       "dependencies": {
463 |         "p-limit": "^2.2.0"
464 |       },
465 |       "engines": {
466 |         "node": ">=8"
467 |       }
468 |     },
469 |     "node_modules/p-try": {
470 |       "version": "2.2.0",
471 |       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
472 |       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
473 |       "dev": true,
474 |       "license": "MIT",
475 |       "engines": {
476 |         "node": ">=6"
477 |       }
478 |     },
479 |     "node_modules/path-exists": {
480 |       "version": "4.0.0",
481 |       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
482 |       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
483 |       "dev": true,
484 |       "license": "MIT",
485 |       "engines": {
486 |         "node": ">=8"
487 |       }
488 |     },
489 |     "node_modules/pegjs": {
490 |       "version": "0.10.0",
491 |       "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
492 |       "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==",
493 |       "dev": true,
494 |       "license": "MIT",
495 |       "bin": {
496 |         "pegjs": "bin/pegjs"
497 |       },
498 |       "engines": {
499 |         "node": ">=0.10"
500 |       }
501 |     },
502 |     "node_modules/pify": {
503 |       "version": "5.0.0",
504 |       "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
505 |       "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
506 |       "dev": true,
507 |       "license": "MIT",
508 |       "engines": {
509 |         "node": ">=10"
510 |       },
511 |       "funding": {
512 |         "url": "https://github.com/sponsors/sindresorhus"
513 |       }
514 |     },
515 |     "node_modules/prettier": {
516 |       "version": "2.8.8",
517 |       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
518 |       "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
519 |       "dev": true,
520 |       "license": "MIT",
521 |       "optional": true,
522 |       "bin": {
523 |         "prettier": "bin-prettier.js"
524 |       },
525 |       "engines": {
526 |         "node": ">=10.13.0"
527 |       },
528 |       "funding": {
529 |         "url": "https://github.com/prettier/prettier?sponsor=1"
530 |       }
531 |     },
532 |     "node_modules/regenerator-runtime": {
533 |       "version": "0.14.1",
534 |       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
535 |       "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
536 |       "dev": true,
537 |       "license": "MIT"
538 |     },
539 |     "node_modules/require-directory": {
540 |       "version": "2.1.1",
541 |       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
542 |       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
543 |       "dev": true,
544 |       "license": "MIT",
545 |       "engines": {
546 |         "node": ">=0.10.0"
547 |       }
548 |     },
549 |     "node_modules/require-main-filename": {
550 |       "version": "2.0.0",
551 |       "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
552 |       "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
553 |       "dev": true,
554 |       "license": "ISC"
555 |     },
556 |     "node_modules/restore-cursor": {
557 |       "version": "3.1.0",
558 |       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
559 |       "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
560 |       "dev": true,
561 |       "license": "MIT",
562 |       "dependencies": {
563 |         "onetime": "^5.1.0",
564 |         "signal-exit": "^3.0.2"
565 |       },
566 |       "engines": {
567 |         "node": ">=8"
568 |       }
569 |     },
570 |     "node_modules/run-async": {
571 |       "version": "2.4.1",
572 |       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
573 |       "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
574 |       "dev": true,
575 |       "license": "MIT",
576 |       "engines": {
577 |         "node": ">=0.12.0"
578 |       }
579 |     },
580 |     "node_modules/rxjs": {
581 |       "version": "6.6.7",
582 |       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
583 |       "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
584 |       "dev": true,
585 |       "license": "Apache-2.0",
586 |       "dependencies": {
587 |         "tslib": "^1.9.0"
588 |       },
589 |       "engines": {
590 |         "npm": ">=2.0.0"
591 |       }
592 |     },
593 |     "node_modules/safer-buffer": {
594 |       "version": "2.1.2",
595 |       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
596 |       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
597 |       "dev": true,
598 |       "license": "MIT"
599 |     },
600 |     "node_modules/set-blocking": {
601 |       "version": "2.0.0",
602 |       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
603 |       "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
604 |       "dev": true,
605 |       "license": "ISC"
606 |     },
607 |     "node_modules/signal-exit": {
608 |       "version": "3.0.7",
609 |       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
610 |       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
611 |       "dev": true,
612 |       "license": "ISC"
613 |     },
614 |     "node_modules/string-width": {
615 |       "version": "4.2.3",
616 |       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
617 |       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
618 |       "dev": true,
619 |       "license": "MIT",
620 |       "dependencies": {
621 |         "emoji-regex": "^8.0.0",
622 |         "is-fullwidth-code-point": "^3.0.0",
623 |         "strip-ansi": "^6.0.1"
624 |       },
625 |       "engines": {
626 |         "node": ">=8"
627 |       }
628 |     },
629 |     "node_modules/strip-ansi": {
630 |       "version": "6.0.1",
631 |       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
632 |       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
633 |       "dev": true,
634 |       "license": "MIT",
635 |       "dependencies": {
636 |         "ansi-regex": "^5.0.1"
637 |       },
638 |       "engines": {
639 |         "node": ">=8"
640 |       }
641 |     },
642 |     "node_modules/supports-color": {
643 |       "version": "7.2.0",
644 |       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
645 |       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
646 |       "dev": true,
647 |       "license": "MIT",
648 |       "dependencies": {
649 |         "has-flag": "^4.0.0"
650 |       },
651 |       "engines": {
652 |         "node": ">=8"
653 |       }
654 |     },
655 |     "node_modules/through": {
656 |       "version": "2.3.8",
657 |       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
658 |       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
659 |       "dev": true,
660 |       "license": "MIT"
661 |     },
662 |     "node_modules/tmp": {
663 |       "version": "0.0.33",
664 |       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
665 |       "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
666 |       "dev": true,
667 |       "license": "MIT",
668 |       "dependencies": {
669 |         "os-tmpdir": "~1.0.2"
670 |       },
671 |       "engines": {
672 |         "node": ">=0.6.0"
673 |       }
674 |     },
675 |     "node_modules/tr46": {
676 |       "version": "0.0.3",
677 |       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
678 |       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
679 |       "dev": true,
680 |       "license": "MIT"
681 |     },
682 |     "node_modules/tslib": {
683 |       "version": "1.14.1",
684 |       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
685 |       "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
686 |       "dev": true,
687 |       "license": "0BSD"
688 |     },
689 |     "node_modules/type-fest": {
690 |       "version": "0.21.3",
691 |       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
692 |       "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
693 |       "dev": true,
694 |       "license": "(MIT OR CC0-1.0)",
695 |       "engines": {
696 |         "node": ">=10"
697 |       },
698 |       "funding": {
699 |         "url": "https://github.com/sponsors/sindresorhus"
700 |       }
701 |     },
702 |     "node_modules/webidl-conversions": {
703 |       "version": "3.0.1",
704 |       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
705 |       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
706 |       "dev": true,
707 |       "license": "BSD-2-Clause"
708 |     },
709 |     "node_modules/whatwg-url": {
710 |       "version": "5.0.0",
711 |       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
712 |       "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
713 |       "dev": true,
714 |       "license": "MIT",
715 |       "dependencies": {
716 |         "tr46": "~0.0.3",
717 |         "webidl-conversions": "^3.0.0"
718 |       }
719 |     },
720 |     "node_modules/which-module": {
721 |       "version": "2.0.1",
722 |       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
723 |       "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
724 |       "dev": true,
725 |       "license": "ISC"
726 |     },
727 |     "node_modules/wrap-ansi": {
728 |       "version": "6.2.0",
729 |       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
730 |       "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
731 |       "dev": true,
732 |       "license": "MIT",
733 |       "dependencies": {
734 |         "ansi-styles": "^4.0.0",
735 |         "string-width": "^4.1.0",
736 |         "strip-ansi": "^6.0.0"
737 |       },
738 |       "engines": {
739 |         "node": ">=8"
740 |       }
741 |     },
742 |     "node_modules/y18n": {
743 |       "version": "4.0.3",
744 |       "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
745 |       "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
746 |       "dev": true,
747 |       "license": "ISC"
748 |     },
749 |     "node_modules/yargs": {
750 |       "version": "15.4.1",
751 |       "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
752 |       "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
753 |       "dev": true,
754 |       "license": "MIT",
755 |       "dependencies": {
756 |         "cliui": "^6.0.0",
757 |         "decamelize": "^1.2.0",
758 |         "find-up": "^4.1.0",
759 |         "get-caller-file": "^2.0.1",
760 |         "require-directory": "^2.1.1",
761 |         "require-main-filename": "^2.0.0",
762 |         "set-blocking": "^2.0.0",
763 |         "string-width": "^4.2.0",
764 |         "which-module": "^2.0.0",
765 |         "y18n": "^4.0.0",
766 |         "yargs-parser": "^18.1.2"
767 |       },
768 |       "engines": {
769 |         "node": ">=8"
770 |       }
771 |     },
772 |     "node_modules/yargs-parser": {
773 |       "version": "18.1.3",
774 |       "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
775 |       "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
776 |       "dev": true,
777 |       "license": "ISC",
778 |       "dependencies": {
779 |         "camelcase": "^5.0.0",
780 |         "decamelize": "^1.2.0"
781 |       },
782 |       "engines": {
783 |         "node": ">=6"
784 |       }
785 |     }
786 |   }
787 | }
788 | 


--------------------------------------------------------------------------------
/package.go:
--------------------------------------------------------------------------------
1 | // Package wasmhttp allows to create a WebAssembly Go HTTP Server embedded in a ServiceWorker.
2 | package wasmhttp
3 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "go-wasm-http-server",
 3 |   "version": "0.1.0",
 4 |   "description": "WebAssembly Go HTTP Server embedded in a ServiceWorker",
 5 |   "main": "index.js",
 6 |   "repository": "https://github.com/nlepage/go-wasm-http-server",
 7 |   "author": "Nicolas Lepage <19571875+nlepage@users.noreply.github.com>",
 8 |   "license": "Apache-2.0",
 9 |   "devDependencies": {
10 |     "all-contributors-cli": "^6.26.1"
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------
/request.go:
--------------------------------------------------------------------------------
  1 | package wasmhttp
  2 | 
  3 | import (
  4 | 	"io"
  5 | 	"net/http"
  6 | 	"net/url"
  7 | 	"syscall/js"
  8 | 
  9 | 	promise "github.com/nlepage/go-js-promise"
 10 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/readablestream"
 11 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
 12 | )
 13 | 
 14 | // Request builds and returns the equivalent http.Request
 15 | func Request(uvalue js.Value) (*http.Request, error) {
 16 | 	value := safejs.Safe(uvalue)
 17 | 
 18 | 	method, err := value.GetString("method")
 19 | 	if err != nil {
 20 | 		return nil, err
 21 | 	}
 22 | 
 23 | 	rawURL, err := value.GetString("url")
 24 | 	if err != nil {
 25 | 		return nil, err
 26 | 	}
 27 | 	u, err := url.Parse(rawURL)
 28 | 	if err != nil {
 29 | 		return nil, err
 30 | 	}
 31 | 
 32 | 	body, err := value.Get("body")
 33 | 	if err != nil {
 34 | 		return nil, err
 35 | 	}
 36 | 
 37 | 	var bodyReader io.ReadCloser
 38 | 
 39 | 	if !body.IsNull() {
 40 | 		// WORKAROUND: Firefox does not have request.body ReadableStream
 41 | 		if body.IsUndefined() {
 42 | 			blobp, err := value.Call("blob")
 43 | 			if err != nil {
 44 | 				return nil, err
 45 | 			}
 46 | 
 47 | 			blob, err := promise.Await(safejs.Unsafe(blobp))
 48 | 			if err != nil {
 49 | 				return nil, err
 50 | 			}
 51 | 
 52 | 			body, err = safejs.Safe(blob).Call("stream")
 53 | 			if err != nil {
 54 | 				return nil, err
 55 | 			}
 56 | 		}
 57 | 
 58 | 		r, err := body.Call("getReader")
 59 | 		if err != nil {
 60 | 			return nil, err
 61 | 		}
 62 | 
 63 | 		bodyReader = readablestream.NewReader(r)
 64 | 	}
 65 | 
 66 | 	req := &http.Request{
 67 | 		Method:     method,
 68 | 		URL:        u,
 69 | 		Body:       bodyReader,
 70 | 		Header:     make(http.Header),
 71 | 		Proto:      "HTTP/1.1",
 72 | 		ProtoMajor: 1,
 73 | 		ProtoMinor: 1,
 74 | 	}
 75 | 
 76 | 	headers, err := value.Get("headers")
 77 | 	if err != nil {
 78 | 		return nil, err
 79 | 	}
 80 | 
 81 | 	headersIt, err := headers.Call("entries")
 82 | 	if err != nil {
 83 | 		return nil, err
 84 | 	}
 85 | 	for {
 86 | 		e, err := headersIt.Call("next")
 87 | 		if err != nil {
 88 | 			return nil, err
 89 | 		}
 90 | 
 91 | 		done, err := e.GetBool("done")
 92 | 		if err != nil {
 93 | 			return nil, err
 94 | 		}
 95 | 
 96 | 		if done {
 97 | 			break
 98 | 		}
 99 | 
100 | 		v, err := e.Get("value")
101 | 		if err != nil {
102 | 			return nil, err
103 | 		}
104 | 
105 | 		key, err := v.IndexString(0)
106 | 		if err != nil {
107 | 			return nil, err
108 | 		}
109 | 
110 | 		value, err := v.IndexString(1)
111 | 		if err != nil {
112 | 			return nil, err
113 | 		}
114 | 
115 | 		req.Header.Set(key, value)
116 | 	}
117 | 
118 | 	return req, nil
119 | }
120 | 


--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
  1 | package wasmhttp
  2 | 
  3 | import (
  4 | 	"bufio"
  5 | 	"context"
  6 | 	"fmt"
  7 | 	"io"
  8 | 	"log/slog"
  9 | 	"net/http"
 10 | 	"syscall/js"
 11 | 
 12 | 	promise "github.com/nlepage/go-js-promise"
 13 | 
 14 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/jstype"
 15 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/readablestream"
 16 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
 17 | )
 18 | 
 19 | type Response interface {
 20 | 	http.ResponseWriter
 21 | 	io.StringWriter
 22 | 	http.Flusher
 23 | 	io.Closer
 24 | 	Context() context.Context
 25 | 	WriteError(string)
 26 | 	JSValue() js.Value
 27 | }
 28 | 
 29 | type response struct {
 30 | 	header      http.Header
 31 | 	wroteHeader bool
 32 | 
 33 | 	promise js.Value
 34 | 	resolve func(any)
 35 | 
 36 | 	rs   *readablestream.Writer
 37 | 	body *bufio.Writer
 38 | }
 39 | 
 40 | func NewResponse() (Response, error) {
 41 | 	rs, err := readablestream.NewWriter()
 42 | 	if err != nil {
 43 | 		return nil, err
 44 | 	}
 45 | 
 46 | 	promise, resolve, _ := promise.New()
 47 | 
 48 | 	return &response{
 49 | 		promise: promise,
 50 | 		resolve: resolve,
 51 | 
 52 | 		rs:   rs,
 53 | 		body: bufio.NewWriter(rs),
 54 | 	}, nil
 55 | }
 56 | 
 57 | var _ Response = (*response)(nil)
 58 | 
 59 | // Header implements [http.ResponseWriter].
 60 | func (r *response) Header() http.Header {
 61 | 	if r.header == nil {
 62 | 		r.header = make(http.Header)
 63 | 	}
 64 | 	return r.header
 65 | }
 66 | 
 67 | func (r *response) headerValue() map[string]any {
 68 | 	h := r.Header()
 69 | 	hh := make(map[string]any, len(h)+1)
 70 | 	for k := range h {
 71 | 		hh[k] = h.Get(k)
 72 | 	}
 73 | 	return hh
 74 | }
 75 | 
 76 | // Write implements http.ResponseWriter.
 77 | func (r *response) Write(buf []byte) (int, error) {
 78 | 	r.writeHeader(buf, "")
 79 | 	return r.body.Write(buf)
 80 | }
 81 | 
 82 | // WriteHeader implements [http.ResponseWriter].
 83 | func (r *response) WriteHeader(code int) {
 84 | 	if r.wroteHeader {
 85 | 		return
 86 | 	}
 87 | 
 88 | 	checkWriteHeaderCode(code)
 89 | 
 90 | 	init, err := safejs.ValueOf(map[string]any{
 91 | 		"status":  code,
 92 | 		"headers": r.headerValue(),
 93 | 	})
 94 | 	if err != nil {
 95 | 		panic(err)
 96 | 	}
 97 | 
 98 | 	res, err := jstype.Response.New(r.rs.Value, init)
 99 | 	if err != nil {
100 | 		panic(err)
101 | 	}
102 | 
103 | 	r.wroteHeader = true
104 | 
105 | 	r.resolve(safejs.Unsafe(res))
106 | }
107 | 
108 | // WriteString implements [io.StringWriter].
109 | func (r *response) WriteString(str string) (int, error) {
110 | 	r.writeHeader(nil, str)
111 | 	return r.body.WriteString(str)
112 | }
113 | 
114 | // Flush implements [http.Flusher]
115 | func (r *response) Flush() {
116 | 	if !r.wroteHeader {
117 | 		r.WriteHeader(200)
118 | 	}
119 | 	if err := r.body.Flush(); err != nil {
120 | 		panic(err)
121 | 	}
122 | }
123 | 
124 | // Close implements [io.Closer]
125 | func (r *response) Close() error {
126 | 	if !r.wroteHeader {
127 | 		r.WriteHeader(200)
128 | 	}
129 | 	if err := r.body.Flush(); err != nil {
130 | 		return err
131 | 	}
132 | 	return r.rs.Close()
133 | }
134 | 
135 | func (r *response) Context() context.Context {
136 | 	return r.rs.Context()
137 | }
138 | 
139 | func (r *response) WriteError(str string) {
140 | 	slog.Error(str)
141 | 	if !r.wroteHeader {
142 | 		r.Header().Set("Content-Type", "text/plain")
143 | 		r.WriteHeader(500)
144 | 		_, _ = r.WriteString(str)
145 | 	}
146 | }
147 | 
148 | func (r *response) JSValue() js.Value {
149 | 	return r.promise
150 | }
151 | 
152 | func (r *response) writeHeader(b []byte, str string) {
153 | 	if r.wroteHeader {
154 | 		return
155 | 	}
156 | 
157 | 	m := r.Header()
158 | 
159 | 	_, hasType := m["Content-Type"]
160 | 	hasTE := m.Get("Transfer-Encoding") != ""
161 | 	if !hasType && !hasTE {
162 | 		if b == nil {
163 | 			if len(str) > 512 {
164 | 				str = str[:512]
165 | 			}
166 | 			b = []byte(str)
167 | 		}
168 | 		m.Set("Content-Type", http.DetectContentType(b))
169 | 	}
170 | 
171 | 	r.WriteHeader(200)
172 | }
173 | 
174 | func checkWriteHeaderCode(code int) {
175 | 	if code < 100 || code > 999 {
176 | 		panic(fmt.Sprintf("invalid WriteHeader code %v", code))
177 | 	}
178 | }
179 | 


--------------------------------------------------------------------------------
/serve.go:
--------------------------------------------------------------------------------
 1 | package wasmhttp
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"net/http"
 7 | 	"strings"
 8 | 	"syscall/js"
 9 | 
10 | 	"github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
11 | )
12 | 
13 | var (
14 | 	wasmhttp = safejs.Safe(js.Global().Get("wasmhttp"))
15 | )
16 | 
17 | // Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
18 | func Serve(handler http.Handler) (func(), error) {
19 | 	h := handler
20 | 	if h == nil {
21 | 		h = http.DefaultServeMux
22 | 	}
23 | 
24 | 	prefix, err := wasmhttp.GetString("path")
25 | 	if err != nil {
26 | 		return nil, err
27 | 	}
28 | 
29 | 	for strings.HasSuffix(prefix, "/") {
30 | 		prefix = strings.TrimSuffix(prefix, "/")
31 | 	}
32 | 
33 | 	if prefix != "" {
34 | 		mux := http.NewServeMux()
35 | 		mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
36 | 		h = mux
37 | 	}
38 | 
39 | 	handlerValue, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) interface{} {
40 | 		res, err := NewResponse()
41 | 		if err != nil {
42 | 			panic(err)
43 | 		}
44 | 
45 | 		go func() {
46 | 			ctx, cancel := context.WithCancel(res.Context())
47 | 
48 | 			defer func() {
49 | 				cancel()
50 | 			}()
51 | 
52 | 			defer func() {
53 | 				if err := res.Close(); err != nil {
54 | 					panic(err)
55 | 				}
56 | 			}()
57 | 
58 | 			defer func() {
59 | 				if r := recover(); r != nil {
60 | 					res.WriteError(fmt.Sprintf("%+v", r))
61 | 				}
62 | 			}()
63 | 
64 | 			req, err := Request(safejs.Unsafe(args[0]))
65 | 			if err != nil {
66 | 				res.WriteError(fmt.Sprintf("%+v", err))
67 | 				return
68 | 			}
69 | 
70 | 			req = req.WithContext(ctx)
71 | 
72 | 			h.ServeHTTP(res, req)
73 | 		}()
74 | 
75 | 		return res.JSValue()
76 | 	})
77 | 	if err != nil {
78 | 		return nil, err
79 | 	}
80 | 
81 | 	if _, err = wasmhttp.Call("setHandler", handlerValue); err != nil {
82 | 		return nil, err
83 | 	}
84 | 
85 | 	return handlerValue.Release, nil
86 | }
87 | 


--------------------------------------------------------------------------------
/sw.js:
--------------------------------------------------------------------------------
 1 | function registerWasmHTTPListener(wasm, { base, cacheName, passthrough, args = [] } = {}) {
 2 |   let path = new URL(registration.scope).pathname
 3 |   if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}`
 4 | 
 5 |   const handlerPromise = new Promise(setHandler => {
 6 |     self.wasmhttp = {
 7 |       path,
 8 |       setHandler,
 9 |     }
10 |   })
11 | 
12 |   const go = new Go()
13 |   go.argv = [wasm, ...args]
14 |   const source = cacheName
15 |     ? caches.open(cacheName).then((cache) => cache.match(wasm)).then((response) => response ?? fetch(wasm))
16 |     : caches.match(wasm).then(response => (response) ?? fetch(wasm))
17 |   WebAssembly.instantiateStreaming(source, go.importObject).then(({ instance }) => go.run(instance))
18 | 
19 |   addEventListener('fetch', e => {
20 |     if (passthrough?.(e.request)) {
21 |         e.respondWith(fetch(e.request))
22 |         return;
23 |     }
24 | 
25 |     const { pathname } = new URL(e.request.url)
26 |     if (!pathname.startsWith(path)) return
27 | 
28 |     e.respondWith(handlerPromise.then(handler => handler(e.request)))
29 |   })
30 | }
31 | 
32 | function trimStart(s, c) {
33 |   let r = s
34 |   while (r.startsWith(c)) r = r.slice(c.length)
35 |   return r
36 | }
37 | 
38 | function trimEnd(s, c) {
39 |   let r = s
40 |   while (r.endsWith(c)) r = r.slice(0, -c.length)
41 |   return r
42 | }
43 | 


--------------------------------------------------------------------------------