@statewalker/webrun-http-browser 0.5.0 → 0.6.0
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 +127 -5
- package/dist/index.js +92 -20
- package/dist/relay/index-sw.d.ts +86 -1
- package/dist/relay/index-sw.d.ts.map +1 -1
- package/dist/relay/index.d.ts +9 -2
- package/dist/relay/index.d.ts.map +1 -1
- package/dist/relay/mount-table.d.ts +48 -0
- package/dist/relay/mount-table.d.ts.map +1 -0
- package/dist/relay/split-service-url.d.ts.map +1 -1
- package/dist/relay-sw.js +275 -49
- package/dist/relay-worker.d.ts +20 -0
- package/dist/relay-worker.d.ts.map +1 -0
- package/dist/relay-worker.js +1899 -0
- package/package.json +7 -3
- package/src/relay/index-sw.ts +288 -37
- package/src/relay/index.ts +60 -4
- package/src/relay/mount-table.ts +115 -0
- package/src/relay/split-service-url.ts +73 -13
- package/src/relay-sw.ts +11 -3
- package/src/relay-worker.ts +20 -0
- package/public-relay/heartbeat.js +0 -31
|
@@ -12,19 +12,79 @@ export interface SplitServiceUrl {
|
|
|
12
12
|
*/
|
|
13
13
|
export function splitServiceUrl(url: URL | string, separator = "~"): SplitServiceUrl {
|
|
14
14
|
const str = `${url}`;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
const empty = { url: str, key: "", baseUrl: "", path: "" };
|
|
16
|
+
|
|
17
|
+
// Strip query and fragment to prevent false positives on `?q=~foo` or `#~FS`.
|
|
18
|
+
// Extract the prefix (origin) and path from the input verbatim, preserving case
|
|
19
|
+
// and form (e.g., `//host` vs. `http://host`, `HTTPS://` vs. `https://`).
|
|
20
|
+
const hashIdx = str.indexOf("#");
|
|
21
|
+
const queryIdx = str.indexOf("?");
|
|
22
|
+
let strippedEnd = str.length;
|
|
23
|
+
if (hashIdx >= 0) strippedEnd = Math.min(strippedEnd, hashIdx);
|
|
24
|
+
if (queryIdx >= 0) strippedEnd = Math.min(strippedEnd, queryIdx);
|
|
25
|
+
const stripped = str.slice(0, strippedEnd);
|
|
26
|
+
|
|
27
|
+
// Identify where the path begins in the input. Three cases:
|
|
28
|
+
// 1. scheme://authority/path — prefix is scheme://authority
|
|
29
|
+
// 2. //authority/path — prefix is //authority
|
|
30
|
+
// 3. relative path — prefix is empty
|
|
31
|
+
let prefixEnd = 0;
|
|
32
|
+
const schemeMatch = stripped.match(/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//);
|
|
33
|
+
if (schemeMatch) {
|
|
34
|
+
// scheme://authority/path — find the next / after the scheme
|
|
35
|
+
prefixEnd = schemeMatch[0].length;
|
|
36
|
+
const slashIdx = stripped.indexOf("/", prefixEnd);
|
|
37
|
+
if (slashIdx >= 0) {
|
|
38
|
+
prefixEnd = slashIdx;
|
|
39
|
+
} else {
|
|
40
|
+
return empty; // No path
|
|
41
|
+
}
|
|
42
|
+
} else if (stripped.startsWith("//")) {
|
|
43
|
+
// //authority/path — find the next / after the //
|
|
44
|
+
prefixEnd = 2;
|
|
45
|
+
const slashIdx = stripped.indexOf("/", prefixEnd);
|
|
46
|
+
if (slashIdx >= 0) {
|
|
47
|
+
prefixEnd = slashIdx;
|
|
48
|
+
} else {
|
|
49
|
+
return empty; // No path
|
|
50
|
+
}
|
|
28
51
|
}
|
|
52
|
+
// else prefixEnd = 0: relative URL
|
|
53
|
+
|
|
54
|
+
const prefix = stripped.slice(0, prefixEnd);
|
|
55
|
+
const pathPart = stripped.slice(prefixEnd);
|
|
56
|
+
|
|
57
|
+
// Check if pathPart starts with the separator, anchored at the segment boundary.
|
|
58
|
+
// With an authority the path always begins with `/`, so the separator must be
|
|
59
|
+
// at `/<separator>`. Without one the input is either ROOT-RELATIVE
|
|
60
|
+
// (`/~FS/a/b` -- `location.pathname`, or any root-absolute href) or plain
|
|
61
|
+
// relative (`~FS/a/b`), and both are accepted: treating "no authority" as
|
|
62
|
+
// "relative" made every root-relative caller get nothing back.
|
|
63
|
+
let keyStart: number;
|
|
64
|
+
let rooted = false;
|
|
65
|
+
if (prefix === "") {
|
|
66
|
+
if (pathPart.startsWith(`/${separator}`)) {
|
|
67
|
+
rooted = true;
|
|
68
|
+
keyStart = separator.length + 1;
|
|
69
|
+
} else if (pathPart.startsWith(separator)) {
|
|
70
|
+
keyStart = separator.length;
|
|
71
|
+
} else {
|
|
72
|
+
return empty;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (!pathPart.startsWith(`/${separator}`)) return empty;
|
|
76
|
+
keyStart = separator.length + 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rest = pathPart.slice(keyStart);
|
|
80
|
+
const slash = rest.indexOf("/");
|
|
81
|
+
const key = slash < 0 ? rest : rest.slice(0, slash);
|
|
82
|
+
if (key === "") return empty;
|
|
83
|
+
|
|
84
|
+
// The base keeps the input's own form: `https://host/~FS/`, `//host/~FS/`,
|
|
85
|
+
// `/~FS/` and `~FS/` each round-trip as written.
|
|
86
|
+
const base = prefix === "" ? (rooted ? "/" : "") : `${prefix}/`;
|
|
87
|
+
const baseUrl = `${base}${separator}${key}${slash < 0 ? "" : "/"}`;
|
|
88
|
+
const path = slash < 0 ? "" : rest.slice(slash + 1);
|
|
29
89
|
return { url: str, key, baseUrl, path };
|
|
30
90
|
}
|
package/src/relay-sw.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/// <reference lib="webworker" />
|
|
2
2
|
|
|
3
|
-
import { startRelayServiceWorker } from "./relay/index-sw.js";
|
|
3
|
+
import { type RelayServiceWorkerOptions, startRelayServiceWorker } from "./relay/index-sw.js";
|
|
4
4
|
|
|
5
|
-
declare const self: ServiceWorkerGlobalScope;
|
|
5
|
+
declare const self: ServiceWorkerGlobalScope & { RELAY_OPTIONS?: RelayServiceWorkerOptions };
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// OPTIONS FROM A GLOBAL, because this bundle is loaded by `importScripts` from
|
|
8
|
+
// a host's own worker script, which runs first and can set them. A classic
|
|
9
|
+
// worker cannot pass arguments any other way, and module service workers are
|
|
10
|
+
// not reachable through the relay page's registration (the bundle is IIFE, and
|
|
11
|
+
// `getRelayWindowMessageHandler` registers with no `type`).
|
|
12
|
+
//
|
|
13
|
+
// Absent, the options are `{}` -- exactly what this file passed before, so a
|
|
14
|
+
// host that only `importScripts`es the bundle sees no change.
|
|
15
|
+
startRelayServiceWorker(self, self.RELAY_OPTIONS ?? {});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relay ServiceWorker runtime, for a host that builds its own worker.
|
|
3
|
+
*
|
|
4
|
+
* TWO WAYS TO SHIP THE RELAY WORKER, and this is the typed one. A host that
|
|
5
|
+
* takes the prebuilt bundle loads `@statewalker/webrun-http-browser/relay-sw`
|
|
6
|
+
* through classic `importScripts` and passes its options in
|
|
7
|
+
* `self.RELAY_OPTIONS`, which cannot be typed from the outside because that
|
|
8
|
+
* bundle ships no declarations. A host that bundles its own worker imports
|
|
9
|
+
* this module instead and calls `startRelayServiceWorker(self, { … })`
|
|
10
|
+
* directly, with `RelayServiceWorkerOptions` to type the options — including
|
|
11
|
+
* `self.RELAY_OPTIONS`, for a host writing the tiny loader script by hand.
|
|
12
|
+
*
|
|
13
|
+
* Kept as its own entry rather than re-exported from the package root: this
|
|
14
|
+
* code only runs inside a ServiceWorker, and the root entry is what pages
|
|
15
|
+
* import.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type { RelayServiceWorkerOptions } from "./relay/index-sw.js";
|
|
19
|
+
export { startRelayServiceWorker } from "./relay/index-sw.js";
|
|
20
|
+
export type { MountSpec } from "./relay/mount-table.js";
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import * as workerTimer from "https://unpkg.com/worker-timers@7.0.74/build/es2019/module.js?module";
|
|
2
|
-
|
|
3
|
-
export function startHeartbit({
|
|
4
|
-
timeout = 10 * 1000,
|
|
5
|
-
url = "./ping", // 10 seconds
|
|
6
|
-
}) {
|
|
7
|
-
return callPeriodically(async () => {
|
|
8
|
-
const _res = await fetch(url);
|
|
9
|
-
}, timeout);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function callPeriodically(action, timeout) {
|
|
13
|
-
let timerId,
|
|
14
|
-
stopped = false;
|
|
15
|
-
async function run() {
|
|
16
|
-
try {
|
|
17
|
-
await action();
|
|
18
|
-
} catch (err) {
|
|
19
|
-
console.error(err);
|
|
20
|
-
}
|
|
21
|
-
timerId = !stopped ? workerTimer.setTimeout(run, timeout) : 0;
|
|
22
|
-
}
|
|
23
|
-
run();
|
|
24
|
-
return () => {
|
|
25
|
-
stopped = true;
|
|
26
|
-
if (timerId) {
|
|
27
|
-
workerTimer.clearTimeout(timerId);
|
|
28
|
-
timerId = 0;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|