fix(web): default the socket to TLS, and cache-bust the deployed bundles
?ws= now takes a host:port and the page supplies the scheme: wss:// when it
is itself served over https, ws:// when over http. An explicit ws:// from an
https page is UPGRADED rather than passed through, because an https origin
refuses it as mixed content -- so passing it through can only produce a
failure that reads like 'the server is not running'. The scheme is not the
caller's to get wrong.
And the deploy now stamps ?v=<sha> onto both 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
made a shipped fix look like it had not shipped, twice, during the first real
browser debugging session.
BOTH halves are stamped, 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 built against a base does not inherit the base's query. Stamping only
the import specifier would refresh the wrapper and keep the stale binary --
exactly the failure this is here to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+32
-3
@@ -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";
|
||||
|
||||
|
||||
+9
-9
@@ -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.
|
||||
-->
|
||||
<header>
|
||||
<h1>improvise</h1>
|
||||
@@ -60,13 +60,13 @@
|
||||
<code>?seed=url</code>. To open a document hosted by a separate
|
||||
process instead, run
|
||||
<code>improvise-ws-server model.improv --port 8787</code> and reload
|
||||
with <code>?ws=wss://host:port</code>. From this https page the
|
||||
address must be <code>wss://</code> — a <code>ws://</code> one is
|
||||
blocked as mixed content — and
|
||||
<code>improvise-ws-server</code> speaks plain <code>ws://</code> only,
|
||||
so it needs TLS terminated in front of it. Served over
|
||||
<code>http://localhost</code> the same page takes
|
||||
<code>?ws=ws://127.0.0.1:8787</code> directly.
|
||||
with <code>?ws=host:port</code>. <strong>The scheme defaults to
|
||||
<code>wss://</code></strong>, because this page is served over https and
|
||||
an https origin refuses a <code>ws://</code> address as mixed content.
|
||||
Note that <code>improvise-ws-server</code> speaks plain
|
||||
<code>ws://</code> only, so reaching it from here means terminating TLS
|
||||
in front of it. Served over <code>http://localhost</code> the same page
|
||||
defaults to <code>ws://</code> and pairs with it directly.
|
||||
</p>
|
||||
|
||||
<main>
|
||||
|
||||
Reference in New Issue
Block a user