Skip to content

Commit 1de9ef1

Browse files
committed
feat(demo): subdomain gateway, readiness probe, IndexedDB cache, interstitial
Service-worker gateway extensions to the existing demo, mirroring the IPFS service-worker-gateway pattern (https://inbrowser.link) but for Swarm: * Subdomain detection in sw.js: <cid>.localhost, <cid>.bzz.localhost, and <cid>.bytes.localhost (the last routes to bee's /bytes endpoint for raw chunk retrieval, the others to /bzz for manifest-style content). The subdomain label is passed verbatim to bee, which resolves it via the cidv1 resolver (swarm-manifest codec 0xfa, keccak-256 multihash, base32 multibase) back to the underlying 32-byte chunk address. * main.js register-and-reload bootstrap: subdomain origins register the SW on first visit, then reload so the controller can intercept the next navigation. * Readiness probe (/__bee_ready) backed by /peers + a 20s SW-side startup grace. Bee bypasses --warmup-time for light nodes (warmupTime=0 in pkg/node/node.go for !FullNodeMode), so kademlia bins haven't populated by the time the API opens. The grace gives them time before we let the page issue retrieval requests. * interstitial.js: polls readiness, surfaces distinct states (warming grace, peer wait, API not ready), and reloads once truly ready so the SW's navigation handler forwards to bee. * IndexedDB-backed localstore mount: chunks + their leveldb index persist across reloads so repeat visits don't re-fetch the same chunks. Other paths stay InMemory so identity / kademlia / accounting state doesn't leak across sessions. * navigate-mode waitUntil(10s) keeps the SW alive after responses so bee's background cache-write goroutines (Cache().Put scheduled async in pkg/storer/netstore.go) have time to flush to IndexedDB before Chrome idle-kills the worker. * Bee error passthrough: 4xx/5xx navigation responses surface bee's actual JSON error message in a terminal page instead of a misleading 'content not reachable' string.
1 parent a2ae4cf commit 1de9ef1

3 files changed

Lines changed: 462 additions & 9 deletions

File tree

wasm-demo/interstitial.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Interstitial script — runs on the "Bee node starting…" page that the
2+
// service worker serves while the in-worker bee node is still warming up
3+
// (no peers / startup-grace not yet elapsed). Polls /__bee_ready and, when
4+
// the SW reports ready, navigates to the page so the SW's navigation
5+
// handler forwards to bee for the real content.
6+
//
7+
// Parameters come from this script tag's own src query string, e.g.
8+
// <script src="/interstitial.js?m=2&g=20000"></script>
9+
// m = minimum peer count (informational; the SW enforces the actual gate)
10+
// g = startup grace ms (informational)
11+
(async () => {
12+
const params = new URL(document.currentScript.src).searchParams;
13+
const MIN = parseInt(params.get("m") || "2", 10);
14+
const s = document.getElementById("s");
15+
16+
function setStatus(text) {
17+
s.textContent = text;
18+
console.log("[bzz-page]", text);
19+
}
20+
21+
function statusFor(body) {
22+
if (body.reason === "grace") {
23+
const sec = Math.ceil((body.graceRemaining || 0) / 1000);
24+
return `Warming up bee node (${sec}s, ${body.peers} peers so far)…`;
25+
}
26+
if (body.reason === "peers") {
27+
return `Connecting to Swarm: ${body.peers} / ${MIN} peers…`;
28+
}
29+
if (body.reason === "api") {
30+
return "Bee API not ready yet…";
31+
}
32+
return `Peers: ${body.peers} / ${MIN}`;
33+
}
34+
35+
// Poll until the SW reports ready, then reload. The SW's navigation
36+
// handler will see the reload as a top-level navigation; since readiness
37+
// is now true, it'll forward the request to bee and return the content
38+
// (or, if the underlying retrieval fails, fall back to this interstitial
39+
// again — at which point we just keep polling).
40+
for (;;) {
41+
await new Promise((r) => setTimeout(r, 2000));
42+
let body;
43+
try {
44+
const r = await fetch("/__bee_ready", { cache: "no-store" });
45+
if (!r.ok) {
46+
setStatus(`Bee API not ready yet (${r.status})…`);
47+
continue;
48+
}
49+
body = await r.json();
50+
} catch {
51+
setStatus("Waiting for service worker…");
52+
continue;
53+
}
54+
setStatus(statusFor(body));
55+
if (body.ready) {
56+
// Bee is ready: navigate so the SW handles the request as a real top-
57+
// level GET. The SW already runs the readiness check in its navigation
58+
// handler, so if bee actually returns 200 we render the content; if
59+
// not, the SW serves the interstitial again and we keep polling.
60+
location.reload();
61+
return;
62+
}
63+
}
64+
})();

wasm-demo/main.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1+
// On "<reference>.localhost" subdomains the service worker serves the bzz
2+
// content in place. The first hit has no controlling SW yet (so the dev server
3+
// returns this bootstrap page), so once the SW is registered and controlling
4+
// the page we reload to let it serve "/bzz/<reference>/".
5+
const onSubdomain =
6+
location.hostname !== "localhost" && location.hostname.endsWith(".localhost");
7+
18
if ("serviceWorker" in navigator) {
29
navigator.serviceWorker
310
.register("/sw_bundle.js", { scope: "/" })
4-
.then((registration) => {
5-
console.log("Service Worker registered successfully:", registration);
11+
.then(() => {
12+
console.log("Service Worker registered successfully");
13+
14+
if (!onSubdomain) return;
15+
16+
if (navigator.serviceWorker.controller) {
17+
// SW already controls this page: reload so it serves the content.
18+
location.reload();
19+
} else {
20+
// Wait until the freshly-installed SW takes control, then reload once.
21+
navigator.serviceWorker.addEventListener(
22+
"controllerchange",
23+
() => location.reload(),
24+
{ once: true },
25+
);
26+
}
627
})
728
.catch((error) => {
829
console.error("Service Worker registration failed:", error);

0 commit comments

Comments
 (0)