diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a687e738..4e0de4c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,29 @@ jobs: cp -rf web/worker-pkg site/worker-pkg rm -f site/pkg/.gitignore site/pkg/package.json rm -f site/worker-pkg/.gitignore site/worker-pkg/package.json + # Cache-bust the two generated bundles. Pages serves everything + # `max-age=600` and the wasm-pack filenames carry no content hash, + # so for ten minutes after a deploy a returning visitor runs the + # PREVIOUS build -- which during improvise-9x6 debugging made a + # shipped fix look like it had not shipped, twice. The specifiers + # are static import strings, so stamping the copies in site/ is + # enough; the tracked sources are left alone and a local + # `http.server` is unaffected. + # Both halves, because a fresh glue file does NOT pull a fresh + # .wasm: the glue locates it with + # `new URL('..._bg.wasm', import.meta.url)`, and a URL constructed + # against a base does not inherit the base's query string. Stamping + # only the import specifier would refresh the wrapper and keep the + # stale binary -- the failure mode this is here to end. + sed -i "s|\./pkg/improvise_wasm_client\.js|&?v=${GITHUB_SHA}|" site/app.js + sed -i "s|\./worker-pkg/improvise_worker_server\.js|&?v=${GITHUB_SHA}|" site/worker.js + sed -i "s|'improvise_wasm_client_bg\.wasm'|'improvise_wasm_client_bg.wasm?v=${GITHUB_SHA}'|" \ + site/pkg/improvise_wasm_client.js + sed -i "s|'improvise_worker_server_bg\.wasm'|'improvise_worker_server_bg.wasm?v=${GITHUB_SHA}'|" \ + site/worker-pkg/improvise_worker_server.js + grep -n "?v=" site/app.js site/worker.js \ + site/pkg/improvise_wasm_client.js \ + site/worker-pkg/improvise_worker_server.js find site -type f | sort du -sh site diff --git a/README.md b/README.md index 38f83045..79e5e003 100644 --- a/README.md +++ b/README.md @@ -207,13 +207,19 @@ document open in the TUI is shared — start the websocket server and pass cargo run -p improvise-ws-server -- examples/demo.improv --port 8787 ``` -then reload with `?ws=ws://127.0.0.1:8787`. +then reload with `?ws=127.0.0.1:8787`. -**That form works only from a page served over `http://localhost`.** From the -deployed https page the address must be `wss://`, because a `ws://` one is -blocked as mixed content — and `improvise-ws-server` speaks plain `ws://` -only, so reaching it from https means terminating TLS in front of it (a -reverse proxy or a tunnel). Native `wss://` is not implemented. +**The scheme is not yours to supply and defaults to TLS.** `?ws=` takes a +`host:port`; the page uses `wss://` when it is itself served over https and +`ws://` when it is served over http, because an https origin refuses a `ws://` +address as mixed content. An explicit `ws://` from an https page is upgraded +rather than passed through — passing it through can only produce a failure +that reads like "the server is not running". + +So the local pairing above works as written. Reaching a server from the +**deployed** https page means terminating TLS in front of it (a reverse proxy +or a tunnel): `improvise-ws-server` speaks plain `ws://` only, and native +`wss://` is not implemented. Caveat worth stating plainly: **no line of the browser JavaScript has ever run.** There is no browser and no headless DOM in the development environment, diff --git a/web/app.js b/web/app.js index e0f0c44e..c37a053d 100644 --- a/web/app.js +++ b/web/app.js @@ -29,11 +29,12 @@ // default an in-page Web Worker hosting the whole App over OPFS // (improvise-9x6). No server process; this is what makes a // GitHub Pages copy of this directory a working application. -// ?ws=URL a websocket to `improvise-ws-server`. That process is how a +// ?ws=host:port +// a websocket to `improvise-ws-server`. That process is how a // document opened in the TUI is shared, so the path is kept; the // presence of the parameter is the whole selection mechanism, // because a second knob naming the same fact is a second thing to -// get wrong. +// get wrong. The scheme defaults to `wss://` — see `socketUrl`. // // Neither is reachable silently. Every transport writes what it is doing to // `#status` — "starting", "connected", "cannot reach …", "disconnected" — so @@ -42,7 +43,35 @@ import init, { Client } from "./pkg/improvise_wasm_client.js"; const params = new URLSearchParams(location.search); -const WS_URL = params.get("ws"); // present => socket, absent => in-page worker +// present => socket, absent => in-page worker. The value is a host:port, and +// the scheme is NOT the caller's to get wrong: see `socketUrl`. +const WS_PEER = params.get("ws"); + +/** + * The websocket URL for `?ws=`, defaulting to TLS. + * + * `wss://` is the default and a bare `host:port` gets it, because the address + * that works from where this page is actually served is the one it should be + * easiest to write. This page ships to GitHub Pages, which is https, and an + * https origin refuses a `ws://` address as mixed content — so a `ws://` + * default would hand the browser a URL that cannot work and report it as a + * connection failure, which reads like "the server is not running". + * + * An explicit `ws://` is honoured only where it can function: a page served + * over plain http, which is the local `python3 -m http.server` case that pairs + * with `improvise-ws-server` (which speaks plain `ws://` only — it has no TLS, + * so reaching it from https means terminating TLS in front of it). From an + * https page an explicit `ws://` is upgraded rather than passed through, since + * passing it through can only produce a failure the user cannot act on. + */ +function socketUrl(peer) { + const secure = location.protocol === "https:"; + if (/^wss:\/\//i.test(peer)) return peer; + if (/^ws:\/\//i.test(peer)) return secure ? peer.replace(/^ws:/i, "wss:") : peer; + return `${secure ? "wss" : "ws"}://${peer}`; +} + +const WS_URL = WS_PEER ? socketUrl(WS_PEER) : null; const DOC = params.get("doc") ?? "demo.improv"; const SEED = params.get("seed") ?? "demo.improv"; diff --git a/web/index.html b/web/index.html index 0f5f32f9..51188856 100644 --- a/web/index.html +++ b/web/index.html @@ -29,8 +29,8 @@ ?doc= and ?seed= name the document and the file it is first seeded from. To talk to a separate `improvise-ws-server` process instead — which is - how a document open in the TUI is shared — pass ?ws=wss://host:port - (or ws:// when this page is served over http://localhost). + how a document open in the TUI is shared — pass ?ws=host:port; the + scheme defaults to wss:// on https and ws:// on http. -->

improvise

@@ -60,13 +60,13 @@ ?seed=url. To open a document hosted by a separate process instead, run improvise-ws-server model.improv --port 8787 and reload - with ?ws=wss://host:port. From this https page the - address must be wss:// — a ws:// one is - blocked as mixed content — and - improvise-ws-server speaks plain ws:// only, - so it needs TLS terminated in front of it. Served over - http://localhost the same page takes - ?ws=ws://127.0.0.1:8787 directly. + with ?ws=host:port. The scheme defaults to + wss://, because this page is served over https and + an https origin refuses a ws:// address as mixed content. + Note that improvise-ws-server speaks plain + ws:// only, so reaching it from here means terminating TLS + in front of it. Served over http://localhost the same page + defaults to ws:// and pairs with it directly.