@statewalker/webrun-http-browser 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -1
- package/demo/demo-1.html +199 -0
- package/demo/demo-2.html +598 -0
- package/demo/demo-3.html +167 -0
- package/dist/index-sw-umd.js +1 -1
- package/dist/index-sw-umd.min.js +1 -1
- package/dist/index-sw.js +1 -1
- package/dist/index-sw.min.js +1 -1
- package/dist/index-umd.js +1 -1
- package/dist/index-umd.min.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.min.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,4 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
This module simulates HTTP server using Service Workers.
|
|
4
4
|
It allows to develop, test, run and debug server-side code directly in the browser.
|
|
5
|
-
After that the same code can be deployed in Deno / Deno Deploy / Cloudflare / Node JS environments (with adapters).
|
|
5
|
+
After that the same code can be deployed in Deno / Deno Deploy / Cloudflare / Node JS environments (with adapters).
|
|
6
|
+
|
|
7
|
+
## Demo
|
|
8
|
+
|
|
9
|
+
* [Demo 1](./demo/demo-1.html) - a dynamic web site with a server-side API, a HTML page and a CSS file
|
|
10
|
+
* [https://observablehq.com/@kotelnikov/webrun-http-service](https://observablehq.com/@kotelnikov/webrun-http-service) - an Observable page demonstrating how it works. You can play with the code here.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
This module provides a lightweight full-stack development environment in the browser:
|
|
15
|
+
* Code, execute and debug the whole stack in the browser. Even without internet connection.
|
|
16
|
+
* Your data and code don’t leave your browser
|
|
17
|
+
* Instant reproducable environment without installation
|
|
18
|
+
* Easily embeddable in your code
|
|
19
|
+
* Client and server-side code in the browser based on standards:
|
|
20
|
+
- Use module imports for scripts
|
|
21
|
+
- Send requests with fetch
|
|
22
|
+
- Handle HTTP queries with the Request/Response API
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## UseCases
|
|
26
|
+
|
|
27
|
+
* Create rich documentations, tutorials, demos
|
|
28
|
+
* Embed in your rich application - in the new generation Notion, Airtable or Figma
|
|
29
|
+
* Deliver self-contained prototype environments to your clients
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
The core of this module is based on the following native browser technologies: ServiceWorker and MessageChannels.
|
|
34
|
+
|
|
35
|
+
A ServiceWorker is used as the "server", intercepting HTTP calls and delegating their handling to registered modules via MessageChannels.
|
|
36
|
+
So in the same browser-based application you can register a standard HTTP endpoint and call it.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
```js
|
|
40
|
+
import { httpService, endpointUrl } from "...";
|
|
41
|
+
|
|
42
|
+
// Server-side code:
|
|
43
|
+
httpService.register(async (request) => { // request: Request
|
|
44
|
+
return new Response("Hello, world!", {
|
|
45
|
+
headers: {
|
|
46
|
+
"Content-Type": "text/plain"
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Client-side code:
|
|
52
|
+
|
|
53
|
+
const res = await fetch(endpointUrl);
|
|
54
|
+
const text = await res.text();
|
|
55
|
+
console.log(text);
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## They Play Well Together...
|
|
60
|
+
|
|
61
|
+
This in-browser HTTP Server allows to implement the following functionalities:
|
|
62
|
+
- Serve your content from your Local Disk:
|
|
63
|
+
- using [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API)
|
|
64
|
+
- using [Origin Private File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API)
|
|
65
|
+
- Add version control with Git - using [IsomorphicGit](https://isomorphic-git.org/)
|
|
66
|
+
- Provide direct P2P sharing with others – via [WebRTC](https://webrtc.org/)
|
|
67
|
+
- Implement client/server applications using persistent SQLite on Origin Private File System - using [SQLite Wasm](https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system/)
|
|
68
|
+
- Deploy your local site on Edge - via [Deno Deploy](https://deno.com/deploy)
|
|
69
|
+
- Distribute your work in any browser via IPFS / [LibP2P](https://github.com/libp2p/js-libp2p)
|
|
70
|
+
- Integration with existing powerful APIs like https://trpc.io/
|
|
71
|
+
|
|
72
|
+
...and everything in the browser!
|
package/demo/demo-1.html
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<h1>Dynamic Site</h1>
|
|
12
|
+
<p>This demo shows how to launch a dynamic site containing the following parts:</p>
|
|
13
|
+
<ul>
|
|
14
|
+
<li><code>/index.html</code> - the main page referencing a CSS and a java script files. Note that the javascript is
|
|
15
|
+
imported using the standard ESM <code>import</code> notation</li>
|
|
16
|
+
<li><code>/index.css</code> - styles for the HTML page.</li>
|
|
17
|
+
<li><code>/index.js</code> - the main application code. This script listens the input box and sent its content to
|
|
18
|
+
the server-side API.</li>
|
|
19
|
+
<li><code>/api/:name</code> - dynamic server-side endpoint running responding to GET HTTP requests from the client.
|
|
20
|
+
This endpoint returns JSON object with a greeting message using the transmitted name with the current time.
|
|
21
|
+
The name of the user is loaded from the URL.
|
|
22
|
+
|
|
23
|
+
<p>Example of the server response:</p>
|
|
24
|
+
<pre>
|
|
25
|
+
{
|
|
26
|
+
"name" : "John",
|
|
27
|
+
"time" : "11:02:59",
|
|
28
|
+
"milliseconds" : "785"
|
|
29
|
+
}
|
|
30
|
+
</pre>
|
|
31
|
+
</li>
|
|
32
|
+
</ul>
|
|
33
|
+
<div id="view"></div>
|
|
34
|
+
|
|
35
|
+
<script type="module">
|
|
36
|
+
import * as WebRunHttp from "https://unpkg.com/@statewalker/webrun-http-browser@0.3/dist/index.js";
|
|
37
|
+
import { Hono } from "https://unpkg.com/hono@3.5/dist/index.js";
|
|
38
|
+
import mime from "https://esm.sh/mime-types";
|
|
39
|
+
|
|
40
|
+
Promise.resolve().then(main).catch(console.error);
|
|
41
|
+
async function main() {
|
|
42
|
+
// Load a Hono request router:
|
|
43
|
+
const router = getRouter();
|
|
44
|
+
|
|
45
|
+
// Create an HTTP Server with the specified name.
|
|
46
|
+
// It uses the given Hono router to dispatch requests
|
|
47
|
+
// between endpoints.
|
|
48
|
+
const serviceBaseUrl = await startHttpService({
|
|
49
|
+
serviceKey: "MY_SITE",
|
|
50
|
+
router
|
|
51
|
+
})
|
|
52
|
+
const siteUrl = `${serviceBaseUrl}/`;
|
|
53
|
+
|
|
54
|
+
// Now we can visualize the site in an iframe iframe:
|
|
55
|
+
const container = document.querySelector("#view");
|
|
56
|
+
const iframe = document.createElement("iframe");
|
|
57
|
+
iframe.width = "100%";
|
|
58
|
+
iframe.height = "500px";
|
|
59
|
+
iframe.src = siteUrl;
|
|
60
|
+
container.appendChild(iframe);
|
|
61
|
+
|
|
62
|
+
// We also append a reference to this site.
|
|
63
|
+
// It allows to open the site in a separate window:
|
|
64
|
+
const a = document.createElement("a");
|
|
65
|
+
a.target = "_blank";
|
|
66
|
+
a.href = siteUrl;
|
|
67
|
+
a.innerText = `Open "~MY_SITE/index.html" in a new window.`;
|
|
68
|
+
container.insertBefore(a, iframe);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function startHttpService({
|
|
72
|
+
serviceKey,
|
|
73
|
+
router
|
|
74
|
+
}) {
|
|
75
|
+
// Step 1: Start a ServiceWorker and open a MessageChannel used to recieve HTTP requests and send responses:
|
|
76
|
+
const connection = await WebRunHttp.newRemoteRelayChannel();
|
|
77
|
+
|
|
78
|
+
// Step 2: Define an HTTP handler
|
|
79
|
+
const serviceBaseUrl = `${connection.baseUrl}~${serviceKey}`;
|
|
80
|
+
const app = new Hono({
|
|
81
|
+
getPath: (req) => req.url.substring(serviceBaseUrl.length)
|
|
82
|
+
});
|
|
83
|
+
// Add the router
|
|
84
|
+
app.route("/", router);
|
|
85
|
+
const httpHandler = (request) => app.fetch(request);
|
|
86
|
+
|
|
87
|
+
// Step 3: Instantiate a "server" using the communcation channel with the ServiceWorker:
|
|
88
|
+
const close = await WebRunHttp.initHttpService(httpHandler, {
|
|
89
|
+
key: serviceKey, // Service prefix
|
|
90
|
+
port: connection.port
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return serviceBaseUrl;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getRouter() {
|
|
97
|
+
const router = new Hono();
|
|
98
|
+
|
|
99
|
+
// Server-side REST API handler:
|
|
100
|
+
router.get("/api/:name", (c) => {
|
|
101
|
+
const { name } = c.req.param()
|
|
102
|
+
const date = new Date();
|
|
103
|
+
const response = {
|
|
104
|
+
message: `Hello ${name}!`,
|
|
105
|
+
time: date.toLocaleTimeString(),
|
|
106
|
+
milliseconds: date.getMilliseconds()
|
|
107
|
+
}
|
|
108
|
+
return c.json(response);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Static files: an object with path names and the corresponding content
|
|
112
|
+
const files = getStaticFiles();
|
|
113
|
+
// Register these files with a basic dispatcher
|
|
114
|
+
// returning the file content to the caller:
|
|
115
|
+
router.get("/*", newStaticFilesDispatcher(files))
|
|
116
|
+
|
|
117
|
+
return router;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function newStaticFilesDispatcher(files) {
|
|
121
|
+
return function showStaticFiles(context) {
|
|
122
|
+
let { url, path } = context.req;
|
|
123
|
+
path = path.replace(/#.*$/, "");
|
|
124
|
+
if (path.match(/\/$/)) {
|
|
125
|
+
path = path + "index.html";
|
|
126
|
+
}
|
|
127
|
+
const result = files[path];
|
|
128
|
+
if (!result) {
|
|
129
|
+
return context.notFound();
|
|
130
|
+
}
|
|
131
|
+
const mimeType = mime.lookup(path) || "application/octet-stream";
|
|
132
|
+
context.header("Content-Type", mimeType);
|
|
133
|
+
context.status(201);
|
|
134
|
+
if (typeof result === "object") {
|
|
135
|
+
return context.json(result);
|
|
136
|
+
} else {
|
|
137
|
+
return context.body(result);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getStaticFiles() {
|
|
143
|
+
return {
|
|
144
|
+
"/index.css": `
|
|
145
|
+
html, body {
|
|
146
|
+
padding: 0;
|
|
147
|
+
margin: 0;
|
|
148
|
+
}
|
|
149
|
+
h1 {
|
|
150
|
+
color : navy;
|
|
151
|
+
}
|
|
152
|
+
`,
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
"/index.js": `
|
|
156
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
157
|
+
const input = document.querySelector("#name");
|
|
158
|
+
input.addEventListener("input", (ev) => {
|
|
159
|
+
ev.preventDefault();
|
|
160
|
+
ev.stopPropagation();
|
|
161
|
+
updateName();
|
|
162
|
+
})
|
|
163
|
+
updateName();
|
|
164
|
+
async function updateName() {
|
|
165
|
+
const name = input.value;
|
|
166
|
+
const res = await fetch("./api/" + encodeURIComponent(name));
|
|
167
|
+
const { message, time, milliseconds } = await res.json();
|
|
168
|
+
const view = document.querySelector("#messages");
|
|
169
|
+
view.innerHTML = "<div>" + message + "</div><div>Current time is " + time + " (and " + milliseconds + " milliseconds).</div>";
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
`,
|
|
173
|
+
"/index.html": `<!DOCTYPE HTML>
|
|
174
|
+
<html>
|
|
175
|
+
<head>
|
|
176
|
+
<meta charset="utf-8" />
|
|
177
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
178
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
179
|
+
<link rel="stylesheet" href="./index.css" />
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<h1>In-Browser Dynamic Site</h1>
|
|
183
|
+
<div>
|
|
184
|
+
Please enter your name:
|
|
185
|
+
<input type="text" id="name" value="John">
|
|
186
|
+
</div>
|
|
187
|
+
<p>Message from the server:</p>
|
|
188
|
+
<div id="messages">
|
|
189
|
+
</div>
|
|
190
|
+
<script type="module" src="./index.js"><\/script>
|
|
191
|
+
</body>
|
|
192
|
+
</html>
|
|
193
|
+
`
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
</script>
|
|
197
|
+
</body>
|
|
198
|
+
|
|
199
|
+
</html>
|
package/demo/demo-2.html
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<div id="view"></div>
|
|
12
|
+
|
|
13
|
+
<script type="module">
|
|
14
|
+
import * as WebRunHttp from "https://unpkg.com/@statewalker/webrun-http-browser@0.3/dist/index.js";
|
|
15
|
+
import { Hono } from "https://unpkg.com/hono@3.5/dist/index.js";
|
|
16
|
+
import mime from "https://esm.sh/mime-types";
|
|
17
|
+
import {
|
|
18
|
+
BrowserFilesApi,
|
|
19
|
+
FilesApi,
|
|
20
|
+
isHandlerAccessible,
|
|
21
|
+
MemFilesApi,
|
|
22
|
+
verifyPermission,
|
|
23
|
+
} from "https://unpkg.com/@statewalker/webrun-files@0.2.0/dist/index.js";
|
|
24
|
+
|
|
25
|
+
Promise.resolve().then(main).catch(console.error);
|
|
26
|
+
async function main() {
|
|
27
|
+
// Load a Hono request router:
|
|
28
|
+
const router = getRouter();
|
|
29
|
+
|
|
30
|
+
// Create an HTTP Server with the specified name.
|
|
31
|
+
// It uses the given Hono router to dispatch requests
|
|
32
|
+
// between endpoints.
|
|
33
|
+
const serviceBaseUrl = await startHttpService({
|
|
34
|
+
serviceKey: "MY_STATIC_SITE_LAUNCHER",
|
|
35
|
+
router
|
|
36
|
+
})
|
|
37
|
+
const siteUrl = `${serviceBaseUrl}/fs/index.html`;
|
|
38
|
+
|
|
39
|
+
// Now we can visualize the site in an iframe iframe:
|
|
40
|
+
const container = document.querySelector("#view");
|
|
41
|
+
|
|
42
|
+
// We also append a reference to this site.
|
|
43
|
+
// It allows to open the site in a separate window:
|
|
44
|
+
const a = document.createElement("a");
|
|
45
|
+
a.target = "_blank";
|
|
46
|
+
a.href = siteUrl;
|
|
47
|
+
a.innerText = siteUrl;
|
|
48
|
+
container.appendChild(a);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function startHttpService({
|
|
52
|
+
serviceKey,
|
|
53
|
+
router
|
|
54
|
+
}) {
|
|
55
|
+
// Step 1: Start a ServiceWorker and open a MessageChannel used to recieve HTTP requests and send responses:
|
|
56
|
+
const connection = await WebRunHttp.newRemoteRelayChannel();
|
|
57
|
+
|
|
58
|
+
// Step 2: Define an HTTP handler
|
|
59
|
+
const serviceBaseUrl = `${connection.baseUrl}~${serviceKey}`;
|
|
60
|
+
const app = new Hono({
|
|
61
|
+
getPath: (req) => req.url.substring(serviceBaseUrl.length)
|
|
62
|
+
});
|
|
63
|
+
// Add the router
|
|
64
|
+
app.route("/", router);
|
|
65
|
+
const httpHandler = (request) => app.fetch(request);
|
|
66
|
+
|
|
67
|
+
// Step 3: Instantiate a "server" using the communcation channel with the ServiceWorker:
|
|
68
|
+
const close = await WebRunHttp.initHttpService(httpHandler, {
|
|
69
|
+
key: serviceKey, // Service prefix
|
|
70
|
+
port: connection.port
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return serviceBaseUrl;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getRouter() {
|
|
77
|
+
const router = new Hono();
|
|
78
|
+
const files = [...document.querySelectorAll("[data-file]")].reduce((index, elm) => {
|
|
79
|
+
const fileName = elm.getAttribute("data-file");
|
|
80
|
+
const value = elm.innerText.replaceAll("<\\/script>", "</" + "script>");
|
|
81
|
+
index[fileName] = value;
|
|
82
|
+
return index;
|
|
83
|
+
}, {})
|
|
84
|
+
router.get("/*", newStaticFilesDispatcher(files))
|
|
85
|
+
return router;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function newStaticFilesDispatcher(files) {
|
|
89
|
+
return function showStaticFiles(context) {
|
|
90
|
+
let { url, path } = context.req;
|
|
91
|
+
path = path.replace(/#.*$/, "");
|
|
92
|
+
if (path.match(/\/$/)) {
|
|
93
|
+
path = path + "index.html";
|
|
94
|
+
}
|
|
95
|
+
const result = files[path];
|
|
96
|
+
if (!result) {
|
|
97
|
+
return context.notFound();
|
|
98
|
+
}
|
|
99
|
+
const mimeType = mime.lookup(path) || "application/octet-stream";
|
|
100
|
+
context.header("Content-Type", mimeType);
|
|
101
|
+
context.status(201);
|
|
102
|
+
if (typeof result === "object") {
|
|
103
|
+
return context.json(result);
|
|
104
|
+
} else {
|
|
105
|
+
return context.body(result);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<script type="template/file" data-file="/fs/files-tree.css">
|
|
112
|
+
.tree {
|
|
113
|
+
--tree-line-width: 1px;
|
|
114
|
+
--tree-line-color: silver;
|
|
115
|
+
--tree-connector-radius: 0.1em;
|
|
116
|
+
--tree-connector-length: 0.5em;
|
|
117
|
+
--tree-connector-height: 0.6em;
|
|
118
|
+
--tree-content-padding: 0.5em;
|
|
119
|
+
position: relative;
|
|
120
|
+
}
|
|
121
|
+
.tree .tree-node {
|
|
122
|
+
position: relative;
|
|
123
|
+
padding-inline-start: var(--tree-connector-length);
|
|
124
|
+
border-inline-start: var(--tree-line-width) solid var(--tree-line-color);
|
|
125
|
+
}
|
|
126
|
+
.tree .tree-node:before {
|
|
127
|
+
content: "";
|
|
128
|
+
position: absolute;
|
|
129
|
+
top: 0;
|
|
130
|
+
left: 0;
|
|
131
|
+
right: 0;
|
|
132
|
+
width: var(--tree-connector-length);
|
|
133
|
+
height: var(--tree-connector-height);
|
|
134
|
+
max-height: 100%;
|
|
135
|
+
margin-inline-start: calc(0em - var(--tree-line-width));
|
|
136
|
+
border-bottom: var(--tree-line-width) solid var(--tree-line-color);
|
|
137
|
+
border-inline-start: var(--tree-line-width) solid var(--tree-line-color);
|
|
138
|
+
border-end-start-radius: var(--tree-connector-radius);
|
|
139
|
+
}
|
|
140
|
+
.tree .tree-node:last-child {
|
|
141
|
+
border-inline-start: var(--tree-line-width) solid transparent;
|
|
142
|
+
}
|
|
143
|
+
.tree .tree-node > .tree-node-content {
|
|
144
|
+
position: relative;
|
|
145
|
+
padding-inline-start: var(--tree-content-padding);
|
|
146
|
+
}
|
|
147
|
+
.tree .tree-node > .tree-node-content:before {
|
|
148
|
+
content: "";
|
|
149
|
+
position: absolute;
|
|
150
|
+
top: var(--tree-connector-height);
|
|
151
|
+
left: 0;
|
|
152
|
+
right: 0;
|
|
153
|
+
width: var(--tree-line-width);
|
|
154
|
+
height: calc(100% - var(--tree-connector-height));
|
|
155
|
+
border-inline-start: var(--tree-line-width) solid var(--tree-line-color);
|
|
156
|
+
}
|
|
157
|
+
.tree .tree-node > .tree-node-content:last-child:before {
|
|
158
|
+
border-inline-start: var(--tree-line-width) solid transparent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.tree {
|
|
162
|
+
--tree-content-padding: 0.1em;
|
|
163
|
+
padding: 1em 1em 1em 2em;
|
|
164
|
+
}
|
|
165
|
+
.tree .tree-node > .tree-node-content {
|
|
166
|
+
display: flex;
|
|
167
|
+
gap: 0.2em;
|
|
168
|
+
}
|
|
169
|
+
.tree .tree-node > .tree-node-content > .icon-dir,
|
|
170
|
+
.tree .tree-node > .tree-node-content > .icon-file {
|
|
171
|
+
display: block;
|
|
172
|
+
width: 1.5em;
|
|
173
|
+
color: gray;
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
<script type="template/file" data-file="/fs/index.html">
|
|
177
|
+
<!DOCTYPE HTML>
|
|
178
|
+
<html>
|
|
179
|
+
<head>
|
|
180
|
+
<meta charset="utf-8" />
|
|
181
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
182
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
183
|
+
<link crossorigin="anonymous" media="all" rel="stylesheet" href="./files-tree.css" />
|
|
184
|
+
</head>
|
|
185
|
+
<body>
|
|
186
|
+
<h3>Local File System</h3>
|
|
187
|
+
<p>
|
|
188
|
+
This page allows you to get read/write access to your local file system and develop the whole site directly in the browser without an HTTP server.
|
|
189
|
+
</p>
|
|
190
|
+
<div>
|
|
191
|
+
<button id="fs-open">Open Workspace</button>
|
|
192
|
+
<button id="fs-redraw-tree" style="display: none;">Redraw Files Tree</button>
|
|
193
|
+
</div>
|
|
194
|
+
<div id="fs-view"></div>
|
|
195
|
+
|
|
196
|
+
<script type="module" src="./js/index.js"><\/script>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<script data-file="/fs/js/deps.js" type="template/file">
|
|
202
|
+
export * as WebRunHttp from "https://unpkg.com/@statewalker/webrun-http-browser@0.3/dist/index.js";
|
|
203
|
+
export { Hono } from "https://unpkg.com/hono@3.5/dist/index.js";
|
|
204
|
+
export {
|
|
205
|
+
BrowserFilesApi,
|
|
206
|
+
verifyPermission,
|
|
207
|
+
} from "https://unpkg.com/@statewalker/webrun-files@0.2.0/dist/index.js";
|
|
208
|
+
export * as mime from "https://esm.sh/mime-types";
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
<script data-file="/fs/js/openRepository.js" type="template/file">
|
|
213
|
+
import { BrowserFilesApi, verifyPermission } from "./deps.js";
|
|
214
|
+
export async function openRepository(readwrite = true) {
|
|
215
|
+
const handler = await window.showDirectoryPicker();
|
|
216
|
+
if (!(await verifyPermission(handler, readwrite))) {
|
|
217
|
+
throw new Error("Access was not granted");
|
|
218
|
+
}
|
|
219
|
+
const fs = new BrowserFilesApi({ rootHandler : handler });
|
|
220
|
+
return fs;
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<script data-file="/fs/js/startHttpService.js" type="template/file">
|
|
225
|
+
import { WebRunHttp, Hono } from "./deps.js";
|
|
226
|
+
|
|
227
|
+
export async function startHttpService({
|
|
228
|
+
serviceKey,
|
|
229
|
+
router
|
|
230
|
+
}) {
|
|
231
|
+
// Step 1: Start a ServiceWorker and open a MessageChannel used to recieve HTTP requests and send responses:
|
|
232
|
+
const connection = await WebRunHttp.newRemoteRelayChannel();
|
|
233
|
+
|
|
234
|
+
// Step 2: Define an HTTP handler
|
|
235
|
+
const serviceBaseUrl = `${connection.baseUrl}~${serviceKey}`;
|
|
236
|
+
const app = new Hono({
|
|
237
|
+
getPath: (req) => req.url.substring(serviceBaseUrl.length)
|
|
238
|
+
});
|
|
239
|
+
// Add the router
|
|
240
|
+
app.route("/", router);
|
|
241
|
+
const httpHandler = (request) => app.fetch(request);
|
|
242
|
+
|
|
243
|
+
// Step 3: Instantiate a "server" using the communcation channel with the ServiceWorker:
|
|
244
|
+
const close = await WebRunHttp.initHttpService(httpHandler, {
|
|
245
|
+
key: serviceKey, // Service prefix
|
|
246
|
+
port: connection.port
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return serviceBaseUrl;
|
|
250
|
+
}
|
|
251
|
+
</script>
|
|
252
|
+
|
|
253
|
+
<script data-file="/fs/js/newFilesRouter.js" type="template/file">
|
|
254
|
+
import { Hono, WebRunHttp } from "./deps.js";
|
|
255
|
+
|
|
256
|
+
export function newFilesRouter(fs) {
|
|
257
|
+
const router = new Hono();
|
|
258
|
+
router.get("/*", async (context) => {
|
|
259
|
+
let { url, path } = context.req;
|
|
260
|
+
path = path.replace(/#.*$/, "");
|
|
261
|
+
if (path.match(/\/$/)) {
|
|
262
|
+
path = path + "index.html";
|
|
263
|
+
}
|
|
264
|
+
const fileInfo = await fs.stats(path);
|
|
265
|
+
if (!fileInfo || (fileInfo.kind !== "file")) {
|
|
266
|
+
return context.notFound();
|
|
267
|
+
}
|
|
268
|
+
const content = await fs.read(fileInfo);
|
|
269
|
+
const responseBody = await WebRunHttp.toReadableStream(content);
|
|
270
|
+
return new Response(responseBody, {
|
|
271
|
+
status: 200,
|
|
272
|
+
headers: {
|
|
273
|
+
"Content-Type": fileInfo.type,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
return router;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
</script>
|
|
281
|
+
|
|
282
|
+
<script data-file="/fs/js/index.js" type="template/file">
|
|
283
|
+
console.log("Hell-o!");
|
|
284
|
+
const SERVICE_KEY = "FS"
|
|
285
|
+
import { Hono, WebRunHttp } from "./deps.js";
|
|
286
|
+
import { startHttpService } from "./startHttpService.js";
|
|
287
|
+
import { openRepository } from "./openRepository.js";
|
|
288
|
+
import { newFilesRouter } from "./newFilesRouter.js";
|
|
289
|
+
|
|
290
|
+
Promise.resolve().then(main).catch(console.error);
|
|
291
|
+
|
|
292
|
+
async function main() {
|
|
293
|
+
const openBtn = document.querySelector("#fs-open");
|
|
294
|
+
const refreshBtn = document.querySelector("#fs-redraw-tree");
|
|
295
|
+
let close;
|
|
296
|
+
openBtn.addEventListener("click", async () => {
|
|
297
|
+
if (close) close();
|
|
298
|
+
const fs = await openRepository();
|
|
299
|
+
const router = await newFilesRouter(fs);
|
|
300
|
+
const serviceBaseUrl = await startHttpService({
|
|
301
|
+
serviceKey : SERVICE_KEY,
|
|
302
|
+
router
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
openBtn.parentElement.style.display = "none";
|
|
306
|
+
refreshBtn.style.display = "block";
|
|
307
|
+
renderFilesExplorer(fs, serviceBaseUrl);
|
|
308
|
+
refreshBtn.addEventListener("click", (event) => {
|
|
309
|
+
renderFilesExplorer(fs, serviceBaseUrl);
|
|
310
|
+
});
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function renderFilesExplorer(fs, baseUrl) {
|
|
315
|
+
const container = document.querySelector("#fs-view");
|
|
316
|
+
container.innerHTML = "";
|
|
317
|
+
container.classList.add("tree");
|
|
318
|
+
for await (let file of fs.list("/", { recursive: false })) {
|
|
319
|
+
renderFile({
|
|
320
|
+
fs,
|
|
321
|
+
file,
|
|
322
|
+
baseUrl,
|
|
323
|
+
container
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function renderFile({
|
|
329
|
+
fs,
|
|
330
|
+
file,
|
|
331
|
+
container,
|
|
332
|
+
baseUrl
|
|
333
|
+
}) {
|
|
334
|
+
let path = file.path;
|
|
335
|
+
const isDirectory = file.kind === "directory";
|
|
336
|
+
if (isDirectory) {
|
|
337
|
+
if (path[path.length - 1] !== '/') {
|
|
338
|
+
path += '/';
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const treeNodeElm = newElement("tree-node", container);
|
|
343
|
+
const treeNodeContentElm = newElement("tree-node-content", treeNodeElm);
|
|
344
|
+
const iconElm = newElement("tree-node-icon", treeNodeContentElm);
|
|
345
|
+
const iconCls = isDirectory ? "icon-dir" : "icon-file";
|
|
346
|
+
iconElm.classList.add(iconCls);
|
|
347
|
+
iconElm.innerText = isDirectory ? 'dir' : 'file';
|
|
348
|
+
|
|
349
|
+
const linkElm = newElement("file-name", treeNodeContentElm, "a");
|
|
350
|
+
const fileUrl = baseUrl + path;
|
|
351
|
+
linkElm.setAttribute("href", fileUrl);
|
|
352
|
+
linkElm.setAttribute("target", "_blank");
|
|
353
|
+
linkElm.innerText = file.name;
|
|
354
|
+
|
|
355
|
+
if (isDirectory) {
|
|
356
|
+
let opened = false;
|
|
357
|
+
linkElm.addEventListener("click", async (ev) => {
|
|
358
|
+
ev.preventDefault();
|
|
359
|
+
ev.stopPropagation();
|
|
360
|
+
opened = !opened;
|
|
361
|
+
if (opened) {
|
|
362
|
+
for await (let child of fs.list(file, { recursive: false })) {
|
|
363
|
+
renderFile({
|
|
364
|
+
fs,
|
|
365
|
+
file : child,
|
|
366
|
+
baseUrl,
|
|
367
|
+
container : treeNodeElm
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
let elm = treeNodeElm.querySelector(".tree-node-content");
|
|
372
|
+
elm = elm?.nextElementSibling;
|
|
373
|
+
while (elm) {
|
|
374
|
+
const next = elm.nextElementSibling;
|
|
375
|
+
treeNodeElm.removeChild(elm);
|
|
376
|
+
elm = next;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function newElement(className, parent, name = "div") {
|
|
383
|
+
const div = document.createElement(name);
|
|
384
|
+
className
|
|
385
|
+
.split(/\\s+/)
|
|
386
|
+
.map(s => s.trim())
|
|
387
|
+
.forEach(cls => div.classList.add(cls));
|
|
388
|
+
if (parent) {
|
|
389
|
+
parent.appendChild(div);
|
|
390
|
+
}
|
|
391
|
+
return div;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
</script>
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
<script type="template/file" data-file="/fs/index-1.html">
|
|
399
|
+
<!DOCTYPE HTML>
|
|
400
|
+
<html>
|
|
401
|
+
<head>
|
|
402
|
+
<meta charset="utf-8" />
|
|
403
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
404
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
405
|
+
<link crossorigin="anonymous" media="all" rel="stylesheet" href="./files-tree.css" />
|
|
406
|
+
</head>
|
|
407
|
+
<body>
|
|
408
|
+
<h3>Local File System</h3>
|
|
409
|
+
<p>
|
|
410
|
+
This page allows you to get read/write access to your local file system and develop the whole site directly in the browser without an HTTP server.
|
|
411
|
+
</p>
|
|
412
|
+
|
|
413
|
+
<div>
|
|
414
|
+
<button id="fs-open">Open File System</button>
|
|
415
|
+
<div><small>When you click on this button the browser will ask you to give access to the directory you want to explore in your browser.</small></div>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div>
|
|
419
|
+
<button id="fs-redraw-tree" style="display: none;">Refresh</button>
|
|
420
|
+
<div id="fs-view"></div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<script type="module">
|
|
424
|
+
const SERVICE_KEY = "FS"
|
|
425
|
+
const ENDPOINT_BASE_URL = "https://unpkg.com/@statewalker/webrun-http-browser@0.2.1/public-relay"
|
|
426
|
+
import {
|
|
427
|
+
BrowserFilesApi,
|
|
428
|
+
FilesApi,
|
|
429
|
+
isHandlerAccessible,
|
|
430
|
+
MemFilesApi,
|
|
431
|
+
verifyPermission,
|
|
432
|
+
} from "https://unpkg.com/@statewalker/webrun-files@0.2.0/dist/index.js";
|
|
433
|
+
import * as relayLib from "https://unpkg.com/@statewalker/webrun-http-browser@0.2.1/dist/index.js";
|
|
434
|
+
import { lookup } from "https://esm.sh/mime-types";
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
Promise.resolve().then(main).catch(console.error);
|
|
438
|
+
|
|
439
|
+
async function main() {
|
|
440
|
+
const openBtn = document.querySelector("#fs-open");
|
|
441
|
+
const refreshBtn = document.querySelector("#fs-redraw-tree");
|
|
442
|
+
let close;
|
|
443
|
+
openBtn.addEventListener("click", async () => {
|
|
444
|
+
if (close) close();
|
|
445
|
+
const fs = await openRepository();
|
|
446
|
+
const channel = await relayLib.newRemoteRelayChannel();
|
|
447
|
+
close = () => channel.close();
|
|
448
|
+
await startFileServer(SERVICE_KEY, fs, channel.port);
|
|
449
|
+
openBtn.parentElement.style.display = "none";
|
|
450
|
+
refreshBtn.style.display = "block";
|
|
451
|
+
renderFilesExplorer(fs);
|
|
452
|
+
refreshBtn.addEventListener("click", (event) => {
|
|
453
|
+
renderFilesExplorer(fs);
|
|
454
|
+
});
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
async function renderFilesExplorer(fs) {
|
|
460
|
+
const baseUrl = \`\${ENDPOINT_BASE_URL}/~\${SERVICE_KEY}\`;
|
|
461
|
+
const container = document.querySelector("#fs-view");
|
|
462
|
+
container.innerHTML = "";
|
|
463
|
+
container.classList.add("tree");
|
|
464
|
+
for await (let file of fs.list("/", { recursive: false })) {
|
|
465
|
+
renderFile({
|
|
466
|
+
fs,
|
|
467
|
+
file,
|
|
468
|
+
baseUrl,
|
|
469
|
+
container
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function renderFile({
|
|
475
|
+
fs,
|
|
476
|
+
file,
|
|
477
|
+
container,
|
|
478
|
+
baseUrl
|
|
479
|
+
}) {
|
|
480
|
+
let path = file.path;
|
|
481
|
+
const isDirectory = file.kind === "directory";
|
|
482
|
+
if (isDirectory) {
|
|
483
|
+
if (path[path.length - 1] !== '/') {
|
|
484
|
+
path += '/';
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const treeNodeElm = newElement("tree-node", container);
|
|
489
|
+
const treeNodeContentElm = newElement("tree-node-content", treeNodeElm);
|
|
490
|
+
const iconElm = newElement("tree-node-icon", treeNodeContentElm);
|
|
491
|
+
const iconCls = isDirectory ? "icon-dir" : "icon-file";
|
|
492
|
+
iconElm.classList.add(iconCls);
|
|
493
|
+
iconElm.innerText = isDirectory ? 'dir' : 'file';
|
|
494
|
+
|
|
495
|
+
const linkElm = newElement("file-name", treeNodeContentElm, "a");
|
|
496
|
+
const fileUrl = baseUrl + path;
|
|
497
|
+
linkElm.setAttribute("href", fileUrl);
|
|
498
|
+
linkElm.setAttribute("target", "_blank");
|
|
499
|
+
linkElm.innerText = file.name;
|
|
500
|
+
|
|
501
|
+
if (isDirectory) {
|
|
502
|
+
let opened = false;
|
|
503
|
+
linkElm.addEventListener("click", async (ev) => {
|
|
504
|
+
ev.preventDefault();
|
|
505
|
+
ev.stopPropagation();
|
|
506
|
+
opened = !opened;
|
|
507
|
+
if (opened) {
|
|
508
|
+
for await (let child of fs.list(file, { recursive: false })) {
|
|
509
|
+
renderFile({
|
|
510
|
+
fs,
|
|
511
|
+
file : child,
|
|
512
|
+
baseUrl,
|
|
513
|
+
container : treeNodeElm
|
|
514
|
+
})
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
let elm = treeNodeElm.querySelector(".tree-node-content");
|
|
518
|
+
elm = elm?.nextElementSibling;
|
|
519
|
+
while (elm) {
|
|
520
|
+
const next = elm.nextElementSibling;
|
|
521
|
+
treeNodeElm.removeChild(elm);
|
|
522
|
+
elm = next;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function newElement(className, parent, name = "div") {
|
|
529
|
+
const div = document.createElement(name);
|
|
530
|
+
className
|
|
531
|
+
.split(/\\s+/)
|
|
532
|
+
.map(s => s.trim())
|
|
533
|
+
.forEach(cls => div.classList.add(cls));
|
|
534
|
+
if (parent) {
|
|
535
|
+
parent.appendChild(div);
|
|
536
|
+
}
|
|
537
|
+
return div;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function openRepository(readwrite = true) {
|
|
542
|
+
const handler = await window.showDirectoryPicker();
|
|
543
|
+
if (!(await verifyPermission(handler, readwrite))) {
|
|
544
|
+
throw new Error("Access was not granted");
|
|
545
|
+
}
|
|
546
|
+
const fs = new BrowserFilesApi({ rootHandler : handler });
|
|
547
|
+
return fs;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function startFileServer(serviceKey, fs, port) {
|
|
551
|
+
await relayLib.initHttpService(async (req) => {
|
|
552
|
+
try {
|
|
553
|
+
let path = new URL(req.url).pathname;
|
|
554
|
+
path = path.replace(/^[^~]+~[^\\/]+?(\\/.*)$/, '$1');
|
|
555
|
+
if (path[path.length - 1] === '/') {
|
|
556
|
+
path += 'index.html';
|
|
557
|
+
}
|
|
558
|
+
const mimeType = lookup(path.toLowerCase()) || "application/octet-stream";
|
|
559
|
+
const fileInfo = await fs.stats(path);
|
|
560
|
+
if (!fileInfo || (fileInfo.kind !== "file")) {
|
|
561
|
+
return new Response(JSON.stringify({
|
|
562
|
+
status: 404,
|
|
563
|
+
statusText: "File not found. Path: '" + path + "'.",
|
|
564
|
+
}, null, 2), {
|
|
565
|
+
status: 404,
|
|
566
|
+
statusText : "File not found. Path: '" + path + "'.",
|
|
567
|
+
headers : {
|
|
568
|
+
"Content-Type": "application/json"
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
const content = await fs.read(fileInfo);
|
|
573
|
+
const responseBody = await relayLib.toReadableStream(content);
|
|
574
|
+
return new Response(responseBody, {
|
|
575
|
+
status: 200,
|
|
576
|
+
headers: {
|
|
577
|
+
"Content-Type": fileInfo.type,
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error(error);
|
|
582
|
+
return new Response(JSON.stringify(error), {
|
|
583
|
+
status: 500,
|
|
584
|
+
statusText: error.message,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}, {
|
|
588
|
+
key: serviceKey,
|
|
589
|
+
port,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
<\/script>
|
|
593
|
+
</body>
|
|
594
|
+
</html>
|
|
595
|
+
</script>
|
|
596
|
+
</body>
|
|
597
|
+
|
|
598
|
+
</html>
|
package/demo/demo-3.html
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<div id="view"></div>
|
|
12
|
+
|
|
13
|
+
<script type="module">
|
|
14
|
+
import * as WebRunHttp from "https://unpkg.com/@statewalker/webrun-http-browser@0.3/dist/index.js";
|
|
15
|
+
import { Hono } from "https://unpkg.com/hono@3.5/dist/index.js";
|
|
16
|
+
import mime from "https://esm.sh/mime-types";
|
|
17
|
+
|
|
18
|
+
Promise.resolve().then(main).catch(console.error);
|
|
19
|
+
async function main() {
|
|
20
|
+
// Load a Hono request router:
|
|
21
|
+
const router = getRouter();
|
|
22
|
+
|
|
23
|
+
// Create an HTTP Server with the specified name.
|
|
24
|
+
// It uses the given Hono router to dispatch requests
|
|
25
|
+
// between endpoints.
|
|
26
|
+
const serviceBaseUrl = await startHttpService({
|
|
27
|
+
serviceKey: "MY_SITE",
|
|
28
|
+
router
|
|
29
|
+
})
|
|
30
|
+
const siteUrl = `${serviceBaseUrl}/`;
|
|
31
|
+
|
|
32
|
+
// Now we can visualize the site in an iframe iframe:
|
|
33
|
+
const container = document.querySelector("#view");
|
|
34
|
+
const iframe = document.createElement("iframe");
|
|
35
|
+
iframe.width = "100%";
|
|
36
|
+
iframe.height = "500px";
|
|
37
|
+
iframe.src = siteUrl;
|
|
38
|
+
container.appendChild(iframe);
|
|
39
|
+
|
|
40
|
+
// We also append a reference to this site.
|
|
41
|
+
// It allows to open the site in a separate window:
|
|
42
|
+
const a = document.createElement("a");
|
|
43
|
+
a.target = "_blank";
|
|
44
|
+
a.href = siteUrl;
|
|
45
|
+
a.innerText = siteUrl;
|
|
46
|
+
container.appendChild(a);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function startHttpService({
|
|
50
|
+
serviceKey,
|
|
51
|
+
router
|
|
52
|
+
}) {
|
|
53
|
+
// Step 1: Start a ServiceWorker and open a MessageChannel used to recieve HTTP requests and send responses:
|
|
54
|
+
const connection = await WebRunHttp.newRemoteRelayChannel();
|
|
55
|
+
|
|
56
|
+
// Step 2: Define an HTTP handler
|
|
57
|
+
const serviceBaseUrl = `${connection.baseUrl}~${serviceKey}`;
|
|
58
|
+
const app = new Hono({
|
|
59
|
+
getPath: (req) => req.url.substring(serviceBaseUrl.length)
|
|
60
|
+
});
|
|
61
|
+
// Add the router
|
|
62
|
+
app.route("/", router);
|
|
63
|
+
const httpHandler = (request) => app.fetch(request);
|
|
64
|
+
|
|
65
|
+
// Step 3: Instantiate a "server" using the communcation channel with the ServiceWorker:
|
|
66
|
+
const close = await WebRunHttp.initHttpService(httpHandler, {
|
|
67
|
+
key: serviceKey, // Service prefix
|
|
68
|
+
port: connection.port
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return serviceBaseUrl;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getRouter() {
|
|
75
|
+
const router = new Hono();
|
|
76
|
+
|
|
77
|
+
// Server-side REST API handler:
|
|
78
|
+
router.get("/api/:url", async (c) => {
|
|
79
|
+
const { url } = c.req.param();
|
|
80
|
+
const r = await fetch(url);
|
|
81
|
+
const result = await r.text();
|
|
82
|
+
return c.text(result);
|
|
83
|
+
// c.text(`Hello ${url}! Current timestamp: ${Date.now()}`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Static files
|
|
87
|
+
router.get("/*", newStaticFilesDispatcher({
|
|
88
|
+
"/index.css": `
|
|
89
|
+
html, body {
|
|
90
|
+
padding: 0;
|
|
91
|
+
margin: 0;
|
|
92
|
+
}
|
|
93
|
+
h1 {
|
|
94
|
+
color : navy;
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
"/index.js": `
|
|
100
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
101
|
+
const input = document.querySelector("#url");
|
|
102
|
+
input.addEventListener("input", (ev) => {
|
|
103
|
+
ev.preventDefault();
|
|
104
|
+
ev.stopPropagation();
|
|
105
|
+
updateUrl();
|
|
106
|
+
})
|
|
107
|
+
updateUrl();
|
|
108
|
+
async function updateUrl() {
|
|
109
|
+
const url = input.value;
|
|
110
|
+
const res = await fetch("./api/" + encodeURIComponent(url));
|
|
111
|
+
const text = await res.text();
|
|
112
|
+
const view = document.querySelector("#messages");
|
|
113
|
+
view.innerText = text;
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
`,
|
|
117
|
+
"/index.html": `<!DOCTYPE HTML>
|
|
118
|
+
<html>
|
|
119
|
+
<head>
|
|
120
|
+
<meta charset="utf-8" />
|
|
121
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
122
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
123
|
+
<link rel="stylesheet" href="./index.css" />
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<h1>Hello In-Browser Dynamic Site</h1>
|
|
127
|
+
<div>
|
|
128
|
+
Please enter remote service URL:
|
|
129
|
+
<input type="text" id="url" value="https://dummyjson.com/products">
|
|
130
|
+
</div>
|
|
131
|
+
<p>Message from the server:</p>
|
|
132
|
+
<div id="messages">
|
|
133
|
+
</div>
|
|
134
|
+
<script type="module" src="./index.js"><\/script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
137
|
+
`
|
|
138
|
+
}))
|
|
139
|
+
|
|
140
|
+
return router;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function newStaticFilesDispatcher(files) {
|
|
144
|
+
return function showStaticFiles(context) {
|
|
145
|
+
let { url, path } = context.req;
|
|
146
|
+
path = path.replace(/#.*$/, "");
|
|
147
|
+
if (path.match(/\/$/)) {
|
|
148
|
+
path = path + "index.html";
|
|
149
|
+
}
|
|
150
|
+
const result = files[path];
|
|
151
|
+
if (!result) {
|
|
152
|
+
return context.notFound();
|
|
153
|
+
}
|
|
154
|
+
const mimeType = mime.lookup(path) || "application/octet-stream";
|
|
155
|
+
context.header("Content-Type", mimeType);
|
|
156
|
+
context.status(201);
|
|
157
|
+
if (typeof result === "object") {
|
|
158
|
+
return context.json(result);
|
|
159
|
+
} else {
|
|
160
|
+
return context.body(result);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
165
|
+
</body>
|
|
166
|
+
|
|
167
|
+
</html>
|
package/dist/index-sw-umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
(function (factory) {
|
|
3
3
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
4
|
factory();
|
package/dist/index-sw-umd.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";function t(t){return new Promise(((e,n)=>{t.oncomplete=t.onsuccess=()=>e(t.result),t.onabort=t.onerror=()=>n(t.error)}))}let e;function n(){return e||(e=function(e,n){const r=indexedDB.open(e);r.onupgradeneeded=()=>r.result.createObjectStore(n);const s=t(r);return(t,e)=>s.then((r=>e(r.transaction(n,t).objectStore(n))))}("keyval-store","keyval")),e}function r(e,r=n()){return r("readonly",(n=>t(n.get(e))))}function s(e,r,s=n()){return s("readwrite",(n=>(n.put(r,e),t(n.transaction))))}function a(t){return Object.assign({message:t,stack:t.stack},t)}function o(t){return"string"==typeof t&&(t={message:t}),Object.assign(new Error(t.message),t)}async function i(t,e,n,...r){return new Promise(((s,a)=>{const i=new MessageChannel;i.port1.onmessage=t=>{const{result:e,error:n}=t.data;n?a(o(n)):s(e)},t.postMessage({type:e,params:n},[i.port2,...r])}))}function c(t,e,n){const r=async t=>{if(!t.data||t.data.type!==e)return;const[r,...s]=t.ports;let o={};try{const e=t.data.params;o.result=await n(t,e,...s)}catch(t){o.error=a(t)}r.postMessage(o)};t.addEventListener("message",r);try{t.start&&t.start()}catch(t){}return()=>t.removeEventListener("message",r)}async function*l(t){yield*async function*(t,e=[]){let n,r,s=async(t,n,s)=>{const a={error:t,value:n,done:s};return a.promise=new Promise((t=>a.notify=t)),await e.push(a),r&&r(),r=null,a.promise};const a=t=>s(void 0,t,!1),o=()=>s(void 0,void 0,!0),i=t=>s(t,void 0,!0),c=t(Object.assign([a,o,i],{next:a,complete:o,error:i}));let l;try{for(;;)if(l=await e.shift(),l)try{if(l.error)throw l.error;if(l.done)break;yield l.value}finally{l.notify(!0)}else await(n=r?n:new Promise((t=>r=t)))}finally{for(r&&r(),s=()=>!1,"function"==typeof c&&await c();l=await e.shift();)l.notify(!1)}}((e=>t((async({done:t=!0,value:n,error:r}={})=>{r?await e.error(r):t?await e.complete():await e.next(n)}))))}async function*u(t,e,n={}){const r=new MessageChannel;t.postMessage({type:"START_CALL",params:n},[r.port2]);const s=function(t){let e=[],n=[];const r=async t=>{for(let n of e)await n(t)},s=d({port:t,handler:r});async function*a(){yield*l((t=>(e.push(t),()=>e=e.filter((e=>e!==t)))))}async function o(t){await async function(t,e){let n;try{for await(let n of e)await t({done:!1,value:n})}catch(t){n=t}finally{await t({done:!0,error:n})}}(s.invoke,async function*(){try{n.push(t),yield*t}finally{n=n.filter((e=>e!==t))}}())}return{start:()=>s.start(),close:async()=>{await r({done:!0}),[...n].forEach((t=>t.return&&t.return())),await s.close()},recieveAll:a,sendAll:o}}(r.port1);try{await s.start(),s.sendAll(e),yield*s.recieveAll()}finally{await s.close()}}function d({port:t,handler:e=(()=>{throw new Error("Handler not implemented")}),onError:n=console.error,newCallId:r=(()=>d.__counter=(d.__counter||0)+1)}){const s="REQUEST",i="RESPONSE",c="message",l={},u=async n=>{const r=n.data||{},{type:c,callId:u}=r;if(c===s)try{const{request:s}=r;let a=await e(s,...n.ports||[]);const[o,...c]=Array.isArray(a)?a:void 0!==a?[a]:[];t.postMessage({type:i,callId:u,response:o},c)}catch(e){t.postMessage({type:i,callId:u,error:a(e)})}else if(c===i){const{callId:t,response:e,error:n}=r,s=l[t];delete l[t],s&&(n?s.reject(o(n)):s.resolve(e))}};return{start:async()=>{try{t.addEventListener(c,u),t.start&&await t.start()}catch(t){n(t)}},close:async()=>{try{t.removeEventListener(c,u),t.close&&await t.close()}catch(t){n(t)}},invoke:async function(e={},...n){const a=r();return new Promise(((r,o)=>{try{l[a]={resolve:r,reject:o},t.postMessage({type:s,callId:a,request:e},n)}catch(t){o(t),delete e[a]}}))}}}function f(t){return async e=>{const n=[...(e=await e).headers].map((([t,e])=>[t,e])),r=["url","method","mode","credentials","cache","redirect","referrer","referrerPolicy","integrity","keepalive"].reduce(((t,n)=>{const r=e[n];return void 0!==r&&(t[n]=r),t}),{headers:n}),s=e.body?async function*(t){const e=await t.getReader();let n,r;for(;({done:n,value:r}=await e.read())&&!n;)yield await r}(e.body):[],a=await t({options:r,content:s});let o=null,i={};if(a){i=a.options;const t=a.content;"HEAD"!==r.method&&"OPTIONS"!==r.method?(c=t,o=new ReadableStream({type:"bytes",async pull(t){let e=!1;try{for(;;){const e=await c.next();if(!e||e.done)break;t.enqueue(await e.value)}}catch(n){e=!0,t.error(n)}finally{!e&&t.close()}}})):t&&t.return&&t.return()}else i={status:404,statusText:"Error 404: Not Found"};var c;return new Response(o,i)}}async function y(t,e){const n=f((async e=>await async function(t){t=await t;const{done:e,value:n}=await t.next();let r={};if(!e&&n){const t=(new TextDecoder).decode(n);r=JSON.parse(t)}return{options:r,content:t}}(u(t,async function*(t){const{options:e,content:n}=await t,r=new TextEncoder;yield r.encode(JSON.stringify(e)),yield*n}(e)))));return await n(e)}class w extends Error{constructor(t){super(t.message),this.status=t.status,this.statusText=t.statusText}getResponseOptions(t={}){return Object.assign(this.toJson(),t)}toJson(){return{status:this.status,statusText:this.statusText,message:this.message}}static fromError(t){return t instanceof w||(t=new w(Object.assign({status:500,statusText:"Bad Request",reason:t.message}))),t}static errorResourceNotFound(t={}){return new w({status:404,statusText:"Error 404: Resource not found",...t})}static errorForbidden(t={}){return new w({status:403,statusText:"Error 403: Forbidden",...t})}static errorResourceGone(t={}){return new w({status:410,statusText:"Error 410: Resource Gone",...t})}static errorInternalError(t){return new w({...t,status:500,statusText:"Error 500: Internal error"})}}!function(t){const[e,n]=function(t=console.error){let e=0;const n={},r=r=>{const s=e++;return n[s]=Object.assign((function(e){try{return delete n[s],!e&&r&&r()}catch(e){t(e)}}),{action:r})},s=t=>Object.values(n).forEach((e=>e.action===t&&e(!0))),a=()=>Object.values(n).forEach((t=>t()));return Object.assign([r,a,s],{register:r,clear:a,unregister:s})}();"function"==typeof t.skipWaiting?(console.log("self.skipWaiting() is supported."),t.addEventListener("install",(e=>{e.waitUntil(t.skipWaiting())}))):console.log("self.skipWaiting() is not supported."),t.clients&&"function"==typeof t.clients.claim?(console.log("self.clients.claim() is supported."),t.addEventListener("activate",(e=>{e.waitUntil(t.clients.claim())}))):console.log("self.clients.claim() is not supported.");const a=function({self:t,key:e="clientsIds",get:n,set:r}){let s;async function a(){if(!s){const t=await n(e)||[];s=Object.fromEntries(t)}return s}async function o(){const t=await a(),n=Object.entries(t);return await r(e,n),t}async function i(t,e){const n=await a(),r=e.id;let s=!1;return r in n||(n[t]=r,await o(),s=!0),s}async function c(t){const e=await a();let n=!1;return t in e&&(delete e[t],await o(),n=!0),n}async function l(e){const n=await a(),r=n[e],s=await t.clients.get(r);return s||(delete n[e],await o()),s}return{getClient:l,addClient:i,removeClient:c}}({self:t,get:r,set:s});e(c(t,"REGISTER",(async(t,e)=>await a.addClient(e.key,t.source)))),e(c(t,"UNREGISTER",(async(t,e)=>await a.removeClient(e.key)))),e(c(t,"CONNECT",(async(t,e,n)=>{const{key:r}=e,s=await a.getClient(r);if(!s)throw new Error(`Target client was not found. Target key: "${r}".`);return await i(s,"CONNECT",e,n)})));const o=t=>{const e=t.request,n=function(t,e="~"){let n="",r="",s=t+"";const a=s.indexOf(e);let o="";return a>=0&&(o=s.substring(0,a+e.length),s.substring(a+e.length).replace(/^([^\/]+)/,((t,e)=>(o+=t,o.length<s.length&&(o+="/"),n=e,r=s.substring(o.length),"")))),{url:t,key:n,baseUrl:o,path:r}}(e.url),{key:r}=n;if(r){const s=async()=>{try{const t=new MessageChannel,s=await a.getClient(r);if(!s)throw w.errorResourceGone(n);const o={type:"http",key:r};if(!await i(s,"CONNECT",o,t.port2))throw w.errorForbidden(n);return await y(t.port1,e)}catch(t){const e=(t=w.fromError(t)).getResponseOptions(n);return new Response(JSON.stringify(e),{...e,headers:{"Content-Type":"text/javascript"}})}};t.respondWith(s())}};t.addEventListener("fetch",o),e((()=>t.removeEventListener("fetch",o)))}(self)}));
|
package/dist/index-sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
// See https://github.com/agenjs/agen-utils/blob/main/src/iterator.js
|
|
3
3
|
async function* iterate(init, queue = []) {
|
|
4
4
|
let promise, notify, push = async (error, value, done) => {
|
package/dist/index-sw.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
function t(t){return new Promise(((e,n)=>{t.oncomplete=t.onsuccess=()=>e(t.result),t.onabort=t.onerror=()=>n(t.error)}))}let e;function n(){return e||(e=function(e,n){const r=indexedDB.open(e);r.onupgradeneeded=()=>r.result.createObjectStore(n);const s=t(r);return(t,e)=>s.then((r=>e(r.transaction(n,t).objectStore(n))))}("keyval-store","keyval")),e}function r(e,r=n()){return r("readonly",(n=>t(n.get(e))))}function s(e,r,s=n()){return s("readwrite",(n=>(n.put(r,e),t(n.transaction))))}function a(t){return Object.assign({message:t,stack:t.stack},t)}function o(t){return"string"==typeof t&&(t={message:t}),Object.assign(new Error(t.message),t)}async function i(t,e,n,...r){return new Promise(((s,a)=>{const i=new MessageChannel;i.port1.onmessage=t=>{const{result:e,error:n}=t.data;n?a(o(n)):s(e)},t.postMessage({type:e,params:n},[i.port2,...r])}))}function c(t,e,n){const r=async t=>{if(!t.data||t.data.type!==e)return;const[r,...s]=t.ports;let o={};try{const e=t.data.params;o.result=await n(t,e,...s)}catch(t){o.error=a(t)}r.postMessage(o)};t.addEventListener("message",r);try{t.start&&t.start()}catch(t){}return()=>t.removeEventListener("message",r)}async function*l(t){yield*async function*(t,e=[]){let n,r,s=async(t,n,s)=>{const a={error:t,value:n,done:s};return a.promise=new Promise((t=>a.notify=t)),await e.push(a),r&&r(),r=null,a.promise};const a=t=>s(void 0,t,!1),o=()=>s(void 0,void 0,!0),i=t=>s(t,void 0,!0),c=t(Object.assign([a,o,i],{next:a,complete:o,error:i}));let l;try{for(;;)if(l=await e.shift(),l)try{if(l.error)throw l.error;if(l.done)break;yield l.value}finally{l.notify(!0)}else await(n=r?n:new Promise((t=>r=t)))}finally{for(r&&r(),s=()=>!1,"function"==typeof c&&await c();l=await e.shift();)l.notify(!1)}}((e=>t((async({done:t=!0,value:n,error:r}={})=>{r?await e.error(r):t?await e.complete():await e.next(n)}))))}async function*u(t,e,n={}){const r=new MessageChannel;t.postMessage({type:"START_CALL",params:n},[r.port2]);const s=function(t){let e=[],n=[];const r=async t=>{for(let n of e)await n(t)},s=d({port:t,handler:r});async function*a(){yield*l((t=>(e.push(t),()=>e=e.filter((e=>e!==t)))))}async function o(t){await async function(t,e){let n;try{for await(let n of e)await t({done:!1,value:n})}catch(t){n=t}finally{await t({done:!0,error:n})}}(s.invoke,async function*(){try{n.push(t),yield*t}finally{n=n.filter((e=>e!==t))}}())}return{start:()=>s.start(),close:async()=>{await r({done:!0}),[...n].forEach((t=>t.return&&t.return())),await s.close()},recieveAll:a,sendAll:o}}(r.port1);try{await s.start(),s.sendAll(e),yield*s.recieveAll()}finally{await s.close()}}function d({port:t,handler:e=(()=>{throw new Error("Handler not implemented")}),onError:n=console.error,newCallId:r=(()=>d.__counter=(d.__counter||0)+1)}){const s="REQUEST",i="RESPONSE",c="message",l={},u=async n=>{const r=n.data||{},{type:c,callId:u}=r;if(c===s)try{const{request:s}=r;let a=await e(s,...n.ports||[]);const[o,...c]=Array.isArray(a)?a:void 0!==a?[a]:[];t.postMessage({type:i,callId:u,response:o},c)}catch(e){t.postMessage({type:i,callId:u,error:a(e)})}else if(c===i){const{callId:t,response:e,error:n}=r,s=l[t];delete l[t],s&&(n?s.reject(o(n)):s.resolve(e))}};return{start:async()=>{try{t.addEventListener(c,u),t.start&&await t.start()}catch(t){n(t)}},close:async()=>{try{t.removeEventListener(c,u),t.close&&await t.close()}catch(t){n(t)}},invoke:async function(e={},...n){const a=r();return new Promise(((r,o)=>{try{l[a]={resolve:r,reject:o},t.postMessage({type:s,callId:a,request:e},n)}catch(t){o(t),delete e[a]}}))}}}function f(t){return async e=>{const n=[...(e=await e).headers].map((([t,e])=>[t,e])),r=["url","method","mode","credentials","cache","redirect","referrer","referrerPolicy","integrity","keepalive"].reduce(((t,n)=>{const r=e[n];return void 0!==r&&(t[n]=r),t}),{headers:n}),s=e.body?async function*(t){const e=await t.getReader();let n,r;for(;({done:n,value:r}=await e.read())&&!n;)yield await r}(e.body):[],a=await t({options:r,content:s});let o=null,i={};if(a){i=a.options;const t=a.content;"HEAD"!==r.method&&"OPTIONS"!==r.method?(c=t,o=new ReadableStream({type:"bytes",async pull(t){let e=!1;try{for(;;){const e=await c.next();if(!e||e.done)break;t.enqueue(await e.value)}}catch(n){e=!0,t.error(n)}finally{!e&&t.close()}}})):t&&t.return&&t.return()}else i={status:404,statusText:"Error 404: Not Found"};var c;return new Response(o,i)}}async function y(t,e){const n=f((async e=>await async function(t){t=await t;const{done:e,value:n}=await t.next();let r={};if(!e&&n){const t=(new TextDecoder).decode(n);r=JSON.parse(t)}return{options:r,content:t}}(u(t,async function*(t){const{options:e,content:n}=await t,r=new TextEncoder;yield r.encode(JSON.stringify(e)),yield*n}(e)))));return await n(e)}class w extends Error{constructor(t){super(t.message),this.status=t.status,this.statusText=t.statusText}getResponseOptions(t={}){return Object.assign(this.toJson(),t)}toJson(){return{status:this.status,statusText:this.statusText,message:this.message}}static fromError(t){return t instanceof w||(t=new w(Object.assign({status:500,statusText:"Bad Request",reason:t.message}))),t}static errorResourceNotFound(t={}){return new w({status:404,statusText:"Error 404: Resource not found",...t})}static errorForbidden(t={}){return new w({status:403,statusText:"Error 403: Forbidden",...t})}static errorResourceGone(t={}){return new w({status:410,statusText:"Error 410: Resource Gone",...t})}static errorInternalError(t){return new w({...t,status:500,statusText:"Error 500: Internal error"})}}!function(t){const[e,n]=function(t=console.error){let e=0;const n={},r=r=>{const s=e++;return n[s]=Object.assign((function(e){try{return delete n[s],!e&&r&&r()}catch(e){t(e)}}),{action:r})},s=t=>Object.values(n).forEach((e=>e.action===t&&e(!0))),a=()=>Object.values(n).forEach((t=>t()));return Object.assign([r,a,s],{register:r,clear:a,unregister:s})}();"function"==typeof t.skipWaiting?(console.log("self.skipWaiting() is supported."),t.addEventListener("install",(e=>{e.waitUntil(t.skipWaiting())}))):console.log("self.skipWaiting() is not supported."),t.clients&&"function"==typeof t.clients.claim?(console.log("self.clients.claim() is supported."),t.addEventListener("activate",(e=>{e.waitUntil(t.clients.claim())}))):console.log("self.clients.claim() is not supported.");const a=function({self:t,key:e="clientsIds",get:n,set:r}){let s;async function a(){if(!s){const t=await n(e)||[];s=Object.fromEntries(t)}return s}async function o(){const t=await a(),n=Object.entries(t);return await r(e,n),t}async function i(t,e){const n=await a(),r=e.id;let s=!1;return r in n||(n[t]=r,await o(),s=!0),s}async function c(t){const e=await a();let n=!1;return t in e&&(delete e[t],await o(),n=!0),n}async function l(e){const n=await a(),r=n[e],s=await t.clients.get(r);return s||(delete n[e],await o()),s}return{getClient:l,addClient:i,removeClient:c}}({self:t,get:r,set:s});e(c(t,"REGISTER",(async(t,e)=>await a.addClient(e.key,t.source)))),e(c(t,"UNREGISTER",(async(t,e)=>await a.removeClient(e.key)))),e(c(t,"CONNECT",(async(t,e,n)=>{const{key:r}=e,s=await a.getClient(r);if(!s)throw new Error(`Target client was not found. Target key: "${r}".`);return await i(s,"CONNECT",e,n)})));const o=t=>{const e=t.request,n=function(t,e="~"){let n="",r="",s=t+"";const a=s.indexOf(e);let o="";return a>=0&&(o=s.substring(0,a+e.length),s.substring(a+e.length).replace(/^([^\/]+)/,((t,e)=>(o+=t,o.length<s.length&&(o+="/"),n=e,r=s.substring(o.length),"")))),{url:t,key:n,baseUrl:o,path:r}}(e.url),{key:r}=n;if(r){const s=async()=>{try{const t=new MessageChannel,s=await a.getClient(r);if(!s)throw w.errorResourceGone(n);const o={type:"http",key:r};if(!await i(s,"CONNECT",o,t.port2))throw w.errorForbidden(n);return await y(t.port1,e)}catch(t){const e=(t=w.fromError(t)).getResponseOptions(n);return new Response(JSON.stringify(e),{...e,headers:{"Content-Type":"text/javascript"}})}};t.respondWith(s())}};t.addEventListener("fetch",o),e((()=>t.removeEventListener("fetch",o)))}(self);
|
package/dist/index-umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
package/dist/index-umd.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).statewalker=e.statewalker||{},e.statewalker["webrun-http-browser"]=e.statewalker["webrun-http-browser"]||{}))}(this,(function(e){"use strict";function t(e){return Object.assign({message:e,stack:e.stack},e)}function n(e){return"string"==typeof e&&(e={message:e}),Object.assign(new Error(e.message),e)}async function r(e,t,r,...o){return new Promise(((a,s)=>{const c=new MessageChannel;c.port1.onmessage=e=>{const{result:t,error:r}=e.data;r?s(n(r)):a(t)},e.postMessage({type:t,params:r},[c.port2,...o])}))}function o(e,n,r){const o=async e=>{if(!e.data||e.data.type!==n)return;const[o,...a]=e.ports;let s={};try{const t=e.data.params;s.result=await r(e,t,...a)}catch(e){s.error=t(e)}o.postMessage(s)};e.addEventListener("message",o);try{e.start&&e.start()}catch(e){}return()=>e.removeEventListener("message",o)}function a(e=console.error){let t=0;const n={},r=r=>{const o=t++;return n[o]=Object.assign((function(t){try{return delete n[o],!t&&r&&r()}catch(t){e(t)}}),{action:r})},o=e=>Object.values(n).forEach((t=>t.action===e&&t(!0))),a=()=>Object.values(n).forEach((e=>e()));return Object.assign([r,a,o],{register:r,clear:a,unregister:o})}async function*s(e){yield*async function*(e,t=[]){let n,r,o=async(e,n,o)=>{const a={error:e,value:n,done:o};return a.promise=new Promise((e=>a.notify=e)),await t.push(a),r&&r(),r=null,a.promise};const a=e=>o(void 0,e,!1),s=()=>o(void 0,void 0,!0),c=e=>o(e,void 0,!0),i=e(Object.assign([a,s,c],{next:a,complete:s,error:c}));let l;try{for(;;)if(l=await t.shift(),l)try{if(l.error)throw l.error;if(l.done)break;yield l.value}finally{l.notify(!0)}else await(n=r?n:new Promise((e=>r=e)))}finally{for(r&&r(),o=()=>!1,"function"==typeof i&&await i();l=await t.shift();)l.notify(!1)}}((t=>e((async({done:e=!0,value:n,error:r}={})=>{r?await t.error(r):e?await t.complete():await t.next(n)}))))}async function c(e,t){let n;try{for await(let n of t)await e({done:!1,value:n})}catch(e){n=e}finally{await e({done:!0,error:n})}}async function*i(e,t,n={}){const r=new MessageChannel;e.postMessage({type:"START_CALL",params:n},[r.port2]);const o=u(r.port1);try{await o.start(),o.sendAll(t),yield*o.recieveAll()}finally{await o.close()}}function l(e,t){const n=async e=>{const{type:n,params:r}=e.data||{};if("START_CALL"!==n)return;const o=u(e.ports[0]);try{await o.start();const e=o.recieveAll(),n=await t(e,r);await o.sendAll(n)}finally{await o.close()}};e.addEventListener("message",n);try{e.start&&e.start()}catch(e){}return()=>e.removeEventListener("message",n)}function u(e){let t=[],n=[];const r=async e=>{for(let n of t)await n(e)},o=d({port:e,handler:r});return{start:()=>o.start(),close:async()=>{await r({done:!0}),[...n].forEach((e=>e.return&&e.return())),await o.close()},recieveAll:async function*(){yield*s((e=>(t.push(e),()=>t=t.filter((t=>t!==e)))))},sendAll:async function(e){await c(o.invoke,async function*(){try{n.push(e),yield*e}finally{n=n.filter((t=>t!==e))}}())}}}function d({port:e,handler:r=(()=>{throw new Error("Handler not implemented")}),onError:o=console.error,newCallId:a=(()=>d.__counter=(d.__counter||0)+1)}){const s="REQUEST",c="RESPONSE",i="message",l={},u=async o=>{const a=o.data||{},{type:i,callId:u}=a;if(i===s)try{const{request:t}=a;let n=await r(t,...o.ports||[]);const[s,...i]=Array.isArray(n)?n:void 0!==n?[n]:[];e.postMessage({type:c,callId:u,response:s},i)}catch(n){e.postMessage({type:c,callId:u,error:t(n)})}else if(i===c){const{callId:e,response:t,error:r}=a,o=l[e];delete l[e],o&&(r?o.reject(n(r)):o.resolve(t))}};return{start:async()=>{try{e.addEventListener(i,u),e.start&&await e.start()}catch(e){o(e)}},close:async()=>{try{e.removeEventListener(i,u),e.close&&await e.close()}catch(e){o(e)}},invoke:async function(t={},...n){const r=a();return new Promise(((o,a)=>{try{l[r]={resolve:o,reject:a},e.postMessage({type:s,callId:r,request:t},n)}catch(e){a(e),delete t[r]}}))}}}function f(e){return new ReadableStream({type:"bytes",async pull(t){let n=!1;try{for(;;){const n=await e.next();if(!n||n.done)break;t.enqueue(await n.value)}}catch(e){n=!0,t.error(e)}finally{!n&&t.close()}}})}async function*p(e){const t=await e.getReader();let n,r;for(;({done:n,value:r}=await t.read())&&!n;)yield await r}function y(e){return async t=>{const n=[...(t=await t).headers].map((([e,t])=>[e,t])),r=["url","method","mode","credentials","cache","redirect","referrer","referrerPolicy","integrity","keepalive"].reduce(((e,n)=>{const r=t[n];return void 0!==r&&(e[n]=r),e}),{headers:n}),o=t.body?p(t.body):[],a=await e({options:r,content:o});let s=null,c={};if(a){c=a.options;const e=a.content;"HEAD"!==r.method&&"OPTIONS"!==r.method?s=f(e):e&&e.return&&e.return()}else c={status:404,statusText:"Error 404: Not Found"};return new Response(s,c)}}function w(e){return async t=>{const{options:n,content:r}=await t;let o;const{url:a,mode:s,headers:c=[]}=n,i=new Headers;for(let[e,t]of c)i.append(e,t);delete n.mode,delete n.url,"GET"!==n.method&&"HEAD"!==n.method&&"OPTIONS"!==n.method?(o=f(r),n.duplex="half"):r&&r.return&&r.return();const l=new Request(a,{...n,headers:i,body:o}),u=await e(l);return{options:{status:u.status,statusText:u.statusText,headers:Object.fromEntries([...u.headers])},content:await p(await u.body)}}}async function*m(e){const{options:t,content:n}=await e,r=new TextEncoder;yield r.encode(JSON.stringify(t)),yield*n}async function h(e){e=await e;const{done:t,value:n}=await e.next();let r={};if(!t&&n){const e=(new TextDecoder).decode(n);r=JSON.parse(e)}return{options:r,content:e}}function g(e,t){const n=w(t);return l(e,(e=>m(n(h(e)))))}async function v(e,t){const n=y((async t=>await h(i(e,m(t)))));return await n(t)}function b(){const e=new MessageChannel;return e.port1.onmessage=e=>{navigator.serviceWorker.controller.postMessage(e.data,e.ports)},navigator.serviceWorker.addEventListener("message",(t=>{e.port1.postMessage(t.data,t.ports)})),e.port2}async function E({swUrl:e,scopeUrl:t,type:n}){await navigator.serviceWorker.register(e,{type:n,scope:t});const r=await async function(){return await new Promise((e=>{const t=navigator.serviceWorker;if(t.controller)e(t.controller);else{const n=()=>{e(t.controller),t.removeEventListener("controllerchange",n),t.oncontrollerchange=null};t.addEventListener("controllerchange",n),t.oncontrollerchange=n}}))}();return await async function(e){const t=()=>"activated"===e.state;await new Promise((async n=>{t()?n():e.onstatechange=()=>{t()&&(e.onstatechange=null,n())}}))}(r),r}async function R({key:e,communicationPort:t,...n}){const o=new MessageChannel;return await r(t,"CONNECT",{key:e,...n},o.port2)?o.port1:(o.port1.close(),o.port2.close(),null)}async function k({key:e,handler:t,communicationPort:n}){const[s,c]=a();return await r(n,"REGISTER",{key:e}),s((()=>r(n,"UNREGISTER",{key:e}))),s(o(n,"CONNECT",t)),c}e.callChannel=r,e.callHttpService=async function(e,{key:t,port:n}){const r=await R({key:t,communicationPort:n});return await v(r,e)},e.deserializeError=n,e.fromReadableStream=p,e.getRelayWindowMessageHandler=function({swUrl:e=new URL("./index-sw.js","undefined"==typeof document&&"undefined"==typeof location?new(require("url").URL)("file:"+__filename).href:"undefined"==typeof document?location.href:document.currentScript&&document.currentScript.src||new URL("index-umd.min.js",document.baseURI).href)+"",scopeUrl:t=new URL("../","undefined"==typeof document&&"undefined"==typeof location?new(require("url").URL)("file:"+__filename).href:"undefined"==typeof document?location.href:document.currentScript&&document.currentScript.src||new URL("index-umd.min.js",document.baseURI).href)+""}={}){let n;return async r=>{if("CONNECT"===r.data.type){const o=(r.ports||[])[0];if(n)return void o.close();n=o,await E({swUrl:e,scopeUrl:t});const a=b();a.onmessage=e=>{n.postMessage(e.data,e.ports)},n.onmessage=e=>{a.postMessage(e.data,e.ports)}}}},e.handleChannelCalls=o,e.handleHttpRequests=g,e.handleStreams=l,e.initHttpService=async function(e,{key:t,port:n}){return await k({key:t,communicationPort:n,handler:async(t,n,r)=>(g(r,e),!0)})},e.initServiceWorker=E,e.initServiceWorker1=async function({swUrl:e,scopeUrl:t,type:n}){const{installing:r,waiting:o,active:a}=await navigator.serviceWorker.register(e,{scope:t});(r||o||a).addEventListener("statechange",(e=>{console.log("state",e.target.state)}));const s=navigator.serviceWorker;await new Promise((e=>{if(s.controller)e();else{const t=()=>{e(),s.removeEventListener("controllerchange",t),s.oncontrollerchange=null};s.addEventListener("controllerchange",t),s.oncontrollerchange=t}}));const c=s.controller,i=()=>"activated"===c.state;return await new Promise((async e=>{i()?e(c):c.onstatechange=()=>{i()&&(c.onstatechange=null,e(c))}}))},e.initializeConnection=R,e.newHttpClientStub=y,e.newHttpServerStub=w,e.newInvokationChannel=d,e.newRegistry=a,e.newRemoteRelayChannel=async function({baseUrl:e=new URL("../public-relay/","undefined"==typeof document&&"undefined"==typeof location?new(require("url").URL)("file:"+__filename).href:"undefined"==typeof document?location.href:document.currentScript&&document.currentScript.src||new URL("index-umd.min.js",document.baseURI).href),url:t=new URL("relay.html",e),container:n=document.body}={}){const r=new MessageChannel,{iframe:o,promise:a}=await async function(e){const t=document.createElement("iframe");return t.src=e,Object.assign(t.style,{padding:0,margin:0,border:"none",outline:"none",width:"100%",height:"100%"}),{iframe:t,promise:new Promise(((e,n)=>{try{t.onerror=n,t.onload=()=>e(t)}catch(e){n(e)}}))}}(t);return Object.assign(o.style,{position:"fixed",width:"1px",height:"1px",top:"-1000",left:"-1000",display:"block",opacity:"0",border:"none",outline:"none"}),n.appendChild(o),a.then((()=>{o.contentWindow.postMessage({type:"CONNECT"},"*",[r.port1])})),{baseUrl:e,port:r.port2,close:()=>{o.parentElement&&o.parentElement.removeChild(o),r.port1.close(),r.port2.close()}}},e.newServiceWorkerPort=b,e.recieveData=s,e.registerConnectionsHandler=k,e.sendData=c,e.sendHttpRequest=v,e.sendStream=i,e.serializeError=t,e.splitServiceUrl=function(e,t="~"){let n="",r="",o=e+"";const a=o.indexOf(t);let s="";return a>=0&&(s=o.substring(0,a+t.length),o.substring(a+t.length).replace(/^([^\/]+)/,((e,t)=>(s+=e,s.length<o.length&&(s+="/"),n=t,r=o.substring(s.length),"")))),{url:e,key:n,baseUrl:s,path:r}},e.toReadableStream=f}));
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
function serializeError(error) {
|
|
3
3
|
return Object.assign({
|
|
4
4
|
message: error,
|
package/dist/index.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.
|
|
1
|
+
// @statewalker/webrun-http-browser v0.3.2 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
|
|
2
2
|
function e(e){return Object.assign({message:e,stack:e.stack},e)}function t(e){return"string"==typeof e&&(e={message:e}),Object.assign(new Error(e.message),e)}async function n(e,n,r,...a){return new Promise(((o,s)=>{const c=new MessageChannel;c.port1.onmessage=e=>{const{result:n,error:r}=e.data;r?s(t(r)):o(n)},e.postMessage({type:n,params:r},[c.port2,...a])}))}function r(t,n,r){const a=async t=>{if(!t.data||t.data.type!==n)return;const[a,...o]=t.ports;let s={};try{const e=t.data.params;s.result=await r(t,e,...o)}catch(t){s.error=e(t)}a.postMessage(s)};t.addEventListener("message",a);try{t.start&&t.start()}catch(e){}return()=>t.removeEventListener("message",a)}function a(e=console.error){let t=0;const n={},r=r=>{const a=t++;return n[a]=Object.assign((function(t){try{return delete n[a],!t&&r&&r()}catch(t){e(t)}}),{action:r})},a=e=>Object.values(n).forEach((t=>t.action===e&&t(!0))),o=()=>Object.values(n).forEach((e=>e()));return Object.assign([r,o,a],{register:r,clear:o,unregister:a})}async function*o(e){yield*async function*(e,t=[]){let n,r,a=async(e,n,a)=>{const o={error:e,value:n,done:a};return o.promise=new Promise((e=>o.notify=e)),await t.push(o),r&&r(),r=null,o.promise};const o=e=>a(void 0,e,!1),s=()=>a(void 0,void 0,!0),c=e=>a(e,void 0,!0),i=e(Object.assign([o,s,c],{next:o,complete:s,error:c}));let l;try{for(;;)if(l=await t.shift(),l)try{if(l.error)throw l.error;if(l.done)break;yield l.value}finally{l.notify(!0)}else await(n=r?n:new Promise((e=>r=e)))}finally{for(r&&r(),a=()=>!1,"function"==typeof i&&await i();l=await t.shift();)l.notify(!1)}}((t=>e((async({done:e=!0,value:n,error:r}={})=>{r?await t.error(r):e?await t.complete():await t.next(n)}))))}async function s(e,t){let n;try{for await(let n of t)await e({done:!1,value:n})}catch(e){n=e}finally{await e({done:!0,error:n})}}async function*c(e,t,n={}){const r=new MessageChannel;e.postMessage({type:"START_CALL",params:n},[r.port2]);const a=l(r.port1);try{await a.start(),a.sendAll(t),yield*a.recieveAll()}finally{await a.close()}}function i(e,t){const n=async e=>{const{type:n,params:r}=e.data||{};if("START_CALL"!==n)return;const a=l(e.ports[0]);try{await a.start();const e=a.recieveAll(),n=await t(e,r);await a.sendAll(n)}finally{await a.close()}};e.addEventListener("message",n);try{e.start&&e.start()}catch(e){}return()=>e.removeEventListener("message",n)}function l(e){let t=[],n=[];const r=async e=>{for(let n of t)await n(e)},a=u({port:e,handler:r});return{start:()=>a.start(),close:async()=>{await r({done:!0}),[...n].forEach((e=>e.return&&e.return())),await a.close()},recieveAll:async function*(){yield*o((e=>(t.push(e),()=>t=t.filter((t=>t!==e)))))},sendAll:async function(e){await s(a.invoke,async function*(){try{n.push(e),yield*e}finally{n=n.filter((t=>t!==e))}}())}}}function u({port:n,handler:r=(()=>{throw new Error("Handler not implemented")}),onError:a=console.error,newCallId:o=(()=>u.__counter=(u.__counter||0)+1)}){const s="REQUEST",c="RESPONSE",i="message",l={},d=async a=>{const o=a.data||{},{type:i,callId:u}=o;if(i===s)try{const{request:e}=o;let t=await r(e,...a.ports||[]);const[s,...i]=Array.isArray(t)?t:void 0!==t?[t]:[];n.postMessage({type:c,callId:u,response:s},i)}catch(t){n.postMessage({type:c,callId:u,error:e(t)})}else if(i===c){const{callId:e,response:n,error:r}=o,a=l[e];delete l[e],a&&(r?a.reject(t(r)):a.resolve(n))}};return{start:async()=>{try{n.addEventListener(i,d),n.start&&await n.start()}catch(e){a(e)}},close:async()=>{try{n.removeEventListener(i,d),n.close&&await n.close()}catch(e){a(e)}},invoke:async function(e={},...t){const r=o();return new Promise(((a,o)=>{try{l[r]={resolve:a,reject:o},n.postMessage({type:s,callId:r,request:e},t)}catch(t){o(t),delete e[r]}}))}}}function d(e){return new ReadableStream({type:"bytes",async pull(t){let n=!1;try{for(;;){const n=await e.next();if(!n||n.done)break;t.enqueue(await n.value)}}catch(e){n=!0,t.error(e)}finally{!n&&t.close()}}})}async function*y(e){const t=await e.getReader();let n,r;for(;({done:n,value:r}=await t.read())&&!n;)yield await r}function p(e){return async t=>{const n=[...(t=await t).headers].map((([e,t])=>[e,t])),r=["url","method","mode","credentials","cache","redirect","referrer","referrerPolicy","integrity","keepalive"].reduce(((e,n)=>{const r=t[n];return void 0!==r&&(e[n]=r),e}),{headers:n}),a=t.body?y(t.body):[],o=await e({options:r,content:a});let s=null,c={};if(o){c=o.options;const e=o.content;"HEAD"!==r.method&&"OPTIONS"!==r.method?s=d(e):e&&e.return&&e.return()}else c={status:404,statusText:"Error 404: Not Found"};return new Response(s,c)}}function w(e){return async t=>{const{options:n,content:r}=await t;let a;const{url:o,mode:s,headers:c=[]}=n,i=new Headers;for(let[e,t]of c)i.append(e,t);delete n.mode,delete n.url,"GET"!==n.method&&"HEAD"!==n.method&&"OPTIONS"!==n.method?(a=d(r),n.duplex="half"):r&&r.return&&r.return();const l=new Request(o,{...n,headers:i,body:a}),u=await e(l);return{options:{status:u.status,statusText:u.statusText,headers:Object.fromEntries([...u.headers])},content:await y(await u.body)}}}async function*f(e){const{options:t,content:n}=await e,r=new TextEncoder;yield r.encode(JSON.stringify(t)),yield*n}async function g(e){e=await e;const{done:t,value:n}=await e.next();let r={};if(!t&&n){const e=(new TextDecoder).decode(n);r=JSON.parse(e)}return{options:r,content:e}}function m(e,t){const n=w(t);return i(e,(e=>f(n(g(e)))))}async function h(e,t){const n=p((async t=>await g(c(e,f(t)))));return await n(t)}function v(e,t="~"){let n="",r="",a=e+"";const o=a.indexOf(t);let s="";return o>=0&&(s=a.substring(0,o+t.length),a.substring(o+t.length).replace(/^([^\/]+)/,((e,t)=>(s+=e,s.length<a.length&&(s+="/"),n=t,r=a.substring(s.length),"")))),{url:e,key:n,baseUrl:s,path:r}}function E(){const e=new MessageChannel;return e.port1.onmessage=e=>{navigator.serviceWorker.controller.postMessage(e.data,e.ports)},navigator.serviceWorker.addEventListener("message",(t=>{e.port1.postMessage(t.data,t.ports)})),e.port2}async function b({swUrl:e,scopeUrl:t,type:n}){const{installing:r,waiting:a,active:o}=await navigator.serviceWorker.register(e,{scope:t});(r||a||o).addEventListener("statechange",(e=>{console.log("state",e.target.state)}));const s=navigator.serviceWorker;await new Promise((e=>{if(s.controller)e();else{const t=()=>{e(),s.removeEventListener("controllerchange",t),s.oncontrollerchange=null};s.addEventListener("controllerchange",t),s.oncontrollerchange=t}}));const c=s.controller,i=()=>"activated"===c.state;return await new Promise((async e=>{i()?e(c):c.onstatechange=()=>{i()&&(c.onstatechange=null,e(c))}}))}async function k({swUrl:e,scopeUrl:t,type:n}){await navigator.serviceWorker.register(e,{type:n,scope:t});const r=await async function(){return await new Promise((e=>{const t=navigator.serviceWorker;if(t.controller)e(t.controller);else{const n=()=>{e(t.controller),t.removeEventListener("controllerchange",n),t.oncontrollerchange=null};t.addEventListener("controllerchange",n),t.oncontrollerchange=n}}))}();return await async function(e){const t=()=>"activated"===e.state;await new Promise((async n=>{t()?n():e.onstatechange=()=>{t()&&(e.onstatechange=null,n())}}))}(r),r}async function O(e,{key:t,port:n}){return await U({key:t,communicationPort:n,handler:async(t,n,r)=>(m(r,e),!0)})}async function L(e,{key:t,port:n}){const r=await P({key:t,communicationPort:n});return await h(r,e)}function T({swUrl:e=new URL("./index-sw.js",import.meta.url)+"",scopeUrl:t=new URL("../",import.meta.url)+""}={}){let n;return async r=>{if("CONNECT"===r.data.type){const a=(r.ports||[])[0];if(n)return void a.close();n=a,await k({swUrl:e,scopeUrl:t});const o=E();o.onmessage=e=>{n.postMessage(e.data,e.ports)},n.onmessage=e=>{o.postMessage(e.data,e.ports)}}}}async function C({baseUrl:e=new URL("../public-relay/",import.meta.url),url:t=new URL("relay.html",e),container:n=document.body}={}){const r=new MessageChannel,{iframe:a,promise:o}=await async function(e){const t=document.createElement("iframe");return t.src=e,Object.assign(t.style,{padding:0,margin:0,border:"none",outline:"none",width:"100%",height:"100%"}),{iframe:t,promise:new Promise(((e,n)=>{try{t.onerror=n,t.onload=()=>e(t)}catch(e){n(e)}}))}}(t);return Object.assign(a.style,{position:"fixed",width:"1px",height:"1px",top:"-1000",left:"-1000",display:"block",opacity:"0",border:"none",outline:"none"}),n.appendChild(a),o.then((()=>{a.contentWindow.postMessage({type:"CONNECT"},"*",[r.port1])})),{baseUrl:e,port:r.port2,close:()=>{a.parentElement&&a.parentElement.removeChild(a),r.port1.close(),r.port2.close()}}}async function P({key:e,communicationPort:t,...r}){const a=new MessageChannel;return await n(t,"CONNECT",{key:e,...r},a.port2)?a.port1:(a.port1.close(),a.port2.close(),null)}async function U({key:e,handler:t,communicationPort:o}){const[s,c]=a();return await n(o,"REGISTER",{key:e}),s((()=>n(o,"UNREGISTER",{key:e}))),s(r(o,"CONNECT",t)),c}export{n as callChannel,L as callHttpService,t as deserializeError,y as fromReadableStream,T as getRelayWindowMessageHandler,r as handleChannelCalls,m as handleHttpRequests,i as handleStreams,O as initHttpService,k as initServiceWorker,b as initServiceWorker1,P as initializeConnection,p as newHttpClientStub,w as newHttpServerStub,u as newInvokationChannel,a as newRegistry,C as newRemoteRelayChannel,E as newServiceWorkerPort,o as recieveData,U as registerConnectionsHandler,s as sendData,h as sendHttpRequest,c as sendStream,e as serializeError,v as splitServiceUrl,d as toReadableStream};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-http-browser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Service-worker-based HTTP server simulation for browsers",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/statewalker/statewalker-webrun-http-browser",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"public",
|
|
17
17
|
"public-relay",
|
|
18
18
|
"src/**/*.js",
|
|
19
|
+
"demo",
|
|
19
20
|
"index.js"
|
|
20
21
|
],
|
|
21
22
|
"module": "src/index.js",
|