dev server: answer the production embed URL instead of silently serving the studio
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
A copied embed URL — <bundle>/<profile>[/<instance>]/index.html, the shape the manifest advertises and a person actually pastes — had no route on the dev server, which served its leaf stand-in only at /__leaf/<bundle>/<profile>. It failed silently, which is the expensive part: with no route, Vite's SPA fallback answers an unknown path with the studio's HTML at status 200. The reader asks for one map and receives a different, working page, with no 404 and nothing in the console. leafRewrite() maps the embed shape onto the existing /__leaf/ renderer before any branch runs, so there is one renderer, not two that can drift — the point of the route is that one URL renders the same thing in dev and in the built tree. Both the bare tree-relative form and the /<treeName>/-prefixed form are accepted; the tree name is read from the build config rather than hardcoded. Traversal, encoded traversal and malformed escapes fall through untouched, and an unknown bundle keeps the leaf route's 404 JSON rather than becoming HTML at 200. Verified: /doxa-simple-map/doxa-simple-map/index.html and /__leaf/doxa-simple-map/doxa-simple-map return byte-identical bodies (742 B); the prayer URL mounts doxa-map at 1280x800 with a live canvas in a real browser.
This commit is contained in:
parent
ca4343c25d
commit
d01951bdd6
1 changed files with 64 additions and 1 deletions
|
|
@ -109,6 +109,58 @@ export function galleryModel(reg) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── THE PRODUCTION EMBED URL, ANSWERED BY THE DEV SERVER ─────────────────────────────────────
|
||||
// The manifest advertises, and a person copies, tree-relative embed URLs of the shape
|
||||
// `<bundle>/<profile>/index.html` and `<bundle>/<profile>/<instance>/index.html` — served in
|
||||
// production out of public/<treeName>/. The dev server had NO route for that shape; it served its
|
||||
// leaf stand-in at `/__leaf/<bundle>/<profile>[/<instance>]` only.
|
||||
//
|
||||
// DRIVER-REPORTED 2026-09-23: he copied an embed URL and opened it against his dev server —
|
||||
// http://localhost:5174/doxa-simple-map/doxa-simple-map/prayer/index.html
|
||||
// — and it "did not work". The leaf is real (that exact path serves 200 from the built tree) and
|
||||
// the embed is healthy (it mounts at 700px in a real iframe). The dev server simply had no route.
|
||||
//
|
||||
// ★ AND IT FAILED SILENTLY, which is the part that costs hours. With no route, Vite's SPA fallback
|
||||
// answers an unknown path with the STUDIO'S HTML at status 200. The reader asks for one map and
|
||||
// receives a different, entirely working page, with no 404 and nothing in the console. That is the
|
||||
// same four-surface class as the two other bugs fixed the same day (a manifest 404 returning HTML;
|
||||
// a ../../ child path that 404'd only from a deeper leaf) — see the assetsBaseUrl and
|
||||
// MAP_APP_API_URL notes below, both of which are that shape.
|
||||
//
|
||||
// The fix REWRITES onto the existing `/__leaf/` renderer rather than copying it. Two renderers
|
||||
// would drift, and the entire point of the route is that one embed URL renders the SAME thing in
|
||||
// both environments — a second copy would quietly make that false.
|
||||
//
|
||||
// Both prefixes are accepted because both are real:
|
||||
// /doxa-simple-map/doxa-simple-map/prayer/index.html bare, tree-relative — what the
|
||||
// manifest's `./<b>/<p>/…` snippet
|
||||
// becomes when pasted at the root
|
||||
// (this is the URL the Driver used)
|
||||
// /doxa-maps/doxa-simple-map/doxa-simple-map/prayer/index.html prefixed — the built tree's own
|
||||
// path under public/<treeName>/
|
||||
// `treeName` comes from the build config (`name`, default 'doxa-maps'), never hardcoded, so renaming
|
||||
// the output folder cannot silently strand this route.
|
||||
//
|
||||
// Returns the rewritten `/__leaf/…` path, or '' when the URL is not an embed leaf URL (the common
|
||||
// case — every other request must fall through untouched).
|
||||
export function leafRewrite(path, treeName = 'doxa-maps') {
|
||||
if (!path.endsWith('/index.html')) return ''
|
||||
let segs = path.slice(0, -'/index.html'.length).split('/').filter(Boolean)
|
||||
if (segs[0] === treeName) segs = segs.slice(1) // tolerate the tree-prefixed form
|
||||
// <bundle>/<profile> or <bundle>/<profile>/<instance>. Anything else is not a leaf URL; let it
|
||||
// fall through to Vite rather than guess (a wrong guess here would shadow a real page).
|
||||
if (segs.length < 2 || segs.length > 3) return ''
|
||||
// Traversal guard BEFORE the segments are re-joined into a URL the leaf route will parse. The
|
||||
// leaf route guards again on its own decoded segments; this one stops an encoded `..` from ever
|
||||
// being formed into the rewritten path.
|
||||
for (const s of segs) {
|
||||
let d = s
|
||||
try { d = decodeURIComponent(s) } catch { return '' }
|
||||
if (!d || d === '.' || d === '..' || d.includes('/') || d.includes('\\')) return ''
|
||||
}
|
||||
return '/__leaf/' + segs.join('/')
|
||||
}
|
||||
|
||||
export function autoSpaDevPlugin(token = '') {
|
||||
// One generator for both galleries (internal/generate-gallery.mjs), driven by the same build
|
||||
// config the built page bakes. What dev changes: the model comes from discovery, the live
|
||||
|
|
@ -153,7 +205,18 @@ export function autoSpaDevPlugin(token = '') {
|
|||
apply: 'serve',
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
const path = (req.url || '').split('?')[0]
|
||||
const rawPath = (req.url || '').split('?')[0]
|
||||
// A production embed URL (`<bundle>/<profile>[/<instance>]/index.html`) is rewritten onto
|
||||
// the `/__leaf/` route BEFORE any branch runs, so the existing leaf renderer answers it
|
||||
// unchanged — see leafRewrite above for why the dev server must know this shape at all.
|
||||
// The rewrite is applied to `path` (which every branch reads) and to `req.url` (so anything
|
||||
// downstream sees a consistent request); never via server.middlewares.handle(), which
|
||||
// replays this same middleware and would loop.
|
||||
let treeName = 'doxa-maps'
|
||||
try { treeName = readBuildConfig()?.name || treeName } catch { /* keep the default */ }
|
||||
const rewritten = leafRewrite(rawPath, treeName)
|
||||
const path = rewritten || rawPath
|
||||
if (rewritten) req.url = rewritten + (req.url || '').slice(rawPath.length)
|
||||
// /assets/* — see resolveDevAsset. A miss is a REAL 404, never Vite's HTML
|
||||
// fallback: HTML-for-JSON made three maps die silently at JSON.parse before
|
||||
// they ever created a map, with no console error and no error card.
|
||||
|
|
|
|||
Loading…
Reference in a new issue