From 577149afb498fb9863db9800baddb04db8728568 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:23:43 +0000 Subject: [PATCH] check:experimental-shelf: test the partner embeds too, from another origin Two surfaces join the leaf and the Gallery, each built from the manifest's own snippets so a broken snippet fails as well: B a host on a different origin iframes the leaf; the plugin must draw its data and the host's title, URL and scroll must not move. C a host on a different origin pastes embed.script with its own token and provider. Under maplibre: 0 api.mapbox.com requests. Under mapbox, where the plugin draws a Mapbox map, a second run with a fake host token must show that token, and no other, in tile requests. MapLibre-only plugins take no token and report n/a. The self-test now also poisons the bundle under the script embed. Rows print their seconds; --surfaces A,B,C,D splits the ~26 min run. Measured: 7 bundles, 60 rows PASS, 3 n/a. B does not assert 0 api.mapbox.com: every generated leaf head links Mapbox JS/CSS whatever the provider, which is a template property reported separately. --- internal/check-experimental-shelf.mjs | 111 ++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 15 deletions(-) diff --git a/internal/check-experimental-shelf.mjs b/internal/check-experimental-shelf.mjs index e7221dd99..38dd9d31a 100644 --- a/internal/check-experimental-shelf.mjs +++ b/internal/check-experimental-shelf.mjs @@ -7,8 +7,18 @@ * Both sets are read from plugin-designs/doxa-maps-spa/doxa-maps-spa.vue, so a bundle that joins or * leaves the shelf changes what this gate measures without an edit here. * - * PER BUNDLE, PER ENGINE (mapbox, maplibre), on TWO surfaces: its own leaf page (A) and its tab - * inside the Research Gallery (D). A surface passes when: + * PER BUNDLE, PER ENGINE (mapbox, maplibre), on FOUR surfaces: + * A its own leaf page + * B a host page on a DIFFERENT ORIGIN that iframes the leaf, built from the manifest's own + * embed.iframe snippet; the host's title, URL and scroll must not change + * C a host page on a different origin that pastes the manifest's own embed.script snippet with + * the HOST's token and provider filled in. Under maplibre it must make 0 api.mapbox.com + * requests; under mapbox, where the plugin draws a Mapbox map, a second run with a fake host + * token must show that token in its tile requests and no other (the host's token is used, + * not one baked into the bundle). A MapLibre-only plugin takes no token: reported n/a + * D its tab inside the Research Gallery + * B and C test exactly what a partner pastes, so a broken snippet fails here too. + * A surface passes when: * - the element mounted with content (its shadow tree has real nodes), * - 0 page errors and 0 failed requests (4xx/5xx; Mapbox telemetry and favicons excluded), * - no loading text is left on screen (Loading / Adding / Computing), @@ -36,12 +46,16 @@ import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { spawn } from 'node:child_process' import { createServer } from 'node:net' +import { createServer as createHttpServer } from 'node:http' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const argv = process.argv.slice(2) const arg = (n, d) => { const i = argv.indexOf(n); return i >= 0 && argv[i + 1] ? argv[i + 1] : d } const ENGINES = arg('--engines', 'mapbox,maplibre').split(',') const ONLY = arg('--only', '') ? arg('--only', '').split(',') : null +// --surfaces A,B,C,D (default all). The full shelf is ~60 page loads, ~20 min under SwiftShader: +// run it with BROWSER_MAX_HOLD raised, or split it by --only / --surfaces. +const SURFACES = arg('--surfaces', 'A,B,C,D').split(',') const SELF_TEST = !argv.includes('--no-self-test') const WAIT_MS = Number(process.env.SHELF_WAIT_MS || 75000) @@ -84,6 +98,39 @@ process.on('exit', reap); for (const s of ['SIGINT', 'SIGTERM']) process.on(s, ( const BASE = `http://127.0.0.1:${port}/doxa-maps` for (let i = 0; i < 40; i++) { try { if ((await fetch(BASE + '/manifest.json')).ok) break } catch (_) {} await new Promise(r => setTimeout(r, 250)) } +// ── the PARTNER: a host site on a different origin (localhost vs 127.0.0.1) ──────────────── +// Its pages are built from the manifest's own embed snippets, placeholders filled the way the +// snippet's comments tell a partner to. +const byBundle = Object.fromEntries(manifest.bundles.map(x => [x.bundle, x])) +const snippet = (bundle, profile, kind) => ((byBundle[bundle].profiles.find(x => x.profile === profile) || {}).embed || {})[kind] || '' +function hostPage(kind, s, eng, token) { + let body + if (kind === 'B') { + const leafUrl = `${BASE}/${s.bundle}/${s.profile}/index.html?provider=${eng}` + (eng === 'mapbox' ? `&tk=${token}` : '') + body = snippet(s.bundle, s.profile, 'iframe').replace(/src="[^"]*"/, `src="${leafUrl}&utm_source=shelf"`).replace(/height="\d+"/, 'height="720"') + } else { + body = snippet(s.bundle, s.profile, 'script') + .replaceAll('src="./', `src="${BASE}/`).replaceAll('href="./', `href="${BASE}/`) + .replaceAll('YOUR_MAPBOX_TOKEN', token).replaceAll('YOUR_SOURCE', 'shelf').replaceAll('YOUR_TREE_URL', BASE) + if (eng === 'maplibre') body = body.replace(/\s*]*>\s*/i, '') + .replace(/profile-config='\{/, `profile-config='{"provider":"maplibre",`) + } + return `PARTNER ${kind} + +
A partner's own site
${body}
` +} +const FAKE_TK = 'pk.SHELFHOSTTOKEN' + 'x'.repeat(20) +const hostSrv = createHttpServer((req, res) => { + const m = req.url.match(/^\/(B|C)\/([a-z0-9-]+)\/(mapbox|maplibre)(\/fake)?/) + const s = m && shelf.find(x => x.bundle === m[2]) + if (!s) { res.writeHead(404); return res.end('no') } + res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }) + res.end(hostPage(m[1], s, m[3], m[4] ? FAKE_TK : TK)) +}) +const hostPort = await new Promise(ok => hostSrv.listen(0, '127.0.0.1', () => ok(hostSrv.address().port))) +const HOST = `http://localhost:${hostPort}` +process.on('exit', () => { try { hostSrv.close() } catch (_) {} }) + // ── every Map the page builds, recorded without touching the bundle ─────────── const RECORD_MAPS = () => { window.__shelfMaps = [] @@ -121,14 +168,14 @@ const PROBE = (tag) => `(() => { const layers = (st.layers || []).filter(l => geoIds.includes(l.source)).map(l => l.id) if (layers.length) drawn = m.queryRenderedFeatures({ layers }).length } catch (_) {} - return { loaded: !!(m.__shelfLoaded || (m.isStyleLoaded && m.isStyleLoaded())), geo, drawn } + return { loaded: !!(m.__shelfLoaded || (m.isStyleLoaded && m.isStyleLoaded())), geo, drawn, mapbox: !!(window.mapboxgl && m instanceof window.mapboxgl.Map) } }) return { own, sized, host: !!host, busy, maps } })()`.replace('__TAG__', tag) const b = await chromium.launch({ headless: true, args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--enable-webgl', '--no-sandbox'] }) -async function measure(url, s, poisonBundle) { +async function measure(url, s, poisonBundle, opts = {}) { const p = await b.newPage({ viewport: { width: 1280, height: 800 } }) await p.addInitScript(RECORD_MAPS) const seed = SEED[s.bundle] @@ -137,27 +184,41 @@ async function measure(url, s, poisonBundle) { Storage.prototype.getItem = function (k) { const v = get.call(this, k); return (v == null && String(k).startsWith(keyPrefix)) ? value : v } }, seed) if (poisonBundle) await p.route(new RegExp(`/${poisonBundle}/${poisonBundle}\\.js(\\?|$)`), r => r.fulfill({ status: 200, contentType: 'text/javascript', body: 'throw new Error("shelf self-test: deliberately broken bundle")' })) - const errs = [], fails = [] + const errs = [], fails = [], mapboxReqs = [], tokens = new Set() + p.on('request', r => { const u = r.url(); if (/api\.mapbox\.com/.test(u)) mapboxReqs.push(u.replace(/\?.*/, '').slice(-60)); const t = u.match(/access_token=([^&]+)/); if (t) tokens.add(t[1]) }) p.on('pageerror', e => errs.push(e.message.slice(0, 140))) p.on('response', r => { if (r.status() >= 400 && !/events\.mapbox\.com|favicon/.test(r.url())) fails.push(r.status() + ' ' + r.url().replace(/\?.*/, '').slice(-80)) }) await p.goto(url) + const host0 = opts.frame ? await p.evaluate(() => ({ title: document.title, href: location.href, y: scrollY })) : null + // B: the plugin lives in the iframe whose document comes from the tree + const ctx = () => (opts.frame ? p.frames().find(f => f.url().startsWith(BASE)) : p.mainFrame()) || p.mainFrame() let st = null const t0 = Date.now() - while (Date.now() - t0 < WAIT_MS) { + // provenance needs one tile request, not a drawn map: a fake token cannot draw anything + while (Date.now() - t0 < (opts.provenanceOnly ? Math.min(WAIT_MS, 25000) : WAIT_MS)) { await p.waitForTimeout(2500) - st = await p.evaluate(PROBE(s.tag)) + try { st = await ctx().evaluate(PROBE(s.tag)) } catch (_) { st = null; continue } const mapsReady = st.maps.every(m => m.loaded && m.drawn > 0) - if (st.own > 8 && st.sized && st.busy === 0 && mapsReady && Date.now() - t0 > 6000) break + if (opts.provenanceOnly ? tokens.has(FAKE_TK) : (st.own > 8 && st.sized && st.busy === 0 && mapsReady && Date.now() - t0 > 6000)) break } + const host1 = opts.frame ? await p.evaluate(() => ({ title: document.title, href: location.href, y: scrollY })) : null await p.close() + if (opts.provenanceOnly) { + // C provenance: with a FAKE host token the tiles cannot load, so only one thing is asserted: + // the requests carry the HOST's token, not a token baked into the bundle. + const used = tokens.has(FAKE_TK), baked = [...tokens].filter(t => t !== FAKE_TK) + return { pass: used && !baked.length, why: used ? (baked.length ? [`a token other than the host's was also used (${baked[0].slice(0, 14)}…)`] : []) : ['the host token never reached a tile request'], maps: 0 } + } const why = [] if (!st || !st.host) why.push(`<${s.tag}> never appeared`) else if (st.own <= 8 || !st.sized) why.push(`<${s.tag}> mounted empty (${st.own} node(s)${st.sized ? '' : ', not sized'})`) if (st && st.busy) why.push(`${st.busy} loading label(s) left`) if (st) st.maps.forEach((m, i) => { if (!m.loaded) why.push(`map ${i + 1}: 'load' never fired`); else if (!m.drawn) why.push(`map ${i + 1}: no own data drawn (${m.geo} geojson source(s))`) }) + if (host0 && host1 && (host0.title !== host1.title || host0.href !== host1.href || host0.y !== host1.y)) why.push(`the host page changed (title/URL/scroll): ${JSON.stringify(host0)} -> ${JSON.stringify(host1)}`) + if (opts.noMapbox && mapboxReqs.length) why.push(`${mapboxReqs.length} api.mapbox.com request(s) under maplibre, e.g. ${mapboxReqs[0]}`) if (errs.length) why.push(`page error: ${errs[0]}`) if (fails.length) why.push(`failed request: ${fails[0]}`) - return { pass: !why.length, why, maps: st ? st.maps.length : 0 } + return { pass: !why.length, why, maps: st ? st.maps.length : 0, mapboxMaps: st ? st.maps.filter(m => m.mapbox).length : 0 } } const leaf = (s, eng) => `${BASE}/${s.bundle}/${s.profile}/index.html?provider=${eng}` + (eng === 'mapbox' ? `&tk=${TK}` : '') @@ -168,18 +229,38 @@ try { if (SELF_TEST) { const s = shelf[0] const a = await measure(leaf(s, 'maplibre'), s, s.bundle), d = await measure(gallery(s, 'maplibre'), s, s.bundle) - if (a.pass || d.pass) { console.error(`SELF-TEST FAILED: a deliberately broken ${s.bundle} still passed (${a.pass ? 'leaf' : ''} ${d.pass ? 'gallery' : ''}). This gate cannot see a broken bundle.`); exit = 3 } - else console.log(`self-test: a deliberately broken ${s.bundle} fails as it must (leaf: ${a.why[0]}; gallery: ${d.why[0]})`) + const c = await measure(`${HOST}/C/${s.bundle}/maplibre`, s, s.bundle, { noMapbox: true }) + if (a.pass || d.pass || c.pass) { console.error(`SELF-TEST FAILED: a deliberately broken ${s.bundle} still passed (${a.pass ? 'leaf ' : ''}${d.pass ? 'gallery ' : ''}${c.pass ? 'script-embed' : ''}). This gate cannot see a broken bundle.`); exit = 3 } + else console.log(`self-test: a deliberately broken ${s.bundle} fails as it must (leaf: ${a.why[0]}; script-embed: ${c.why[0]}; gallery: ${d.why[0]})`) } if (exit !== 3) { for (const [k, v] of Object.entries(SEED)) if (shelf.some(s => s.bundle === k)) console.log(`SEEDED ${k}: ${v.why}`) console.log(`EXPERIMENTAL SHELF — ${shelf.length} bundle(s): ${shelf.map(s => s.bundle).join(', ')} · engines: ${ENGINES.join(', ')}`) - for (const s of shelf) for (const eng of ENGINES) for (const [surf, url] of [['A leaf', leaf(s, eng)], ['D gallery', gallery(s, eng)]]) { - const r = await measure(url, s) + // B does NOT assert 0 api.mapbox.com: the iframe loads the LEAF page, and every generated leaf + // links Mapbox's JS/CSS statically whatever the provider (generate-tree's template). + // That is a fleet-wide leaf property, reported separately; C is where a partner chooses. + const row = (r, s, eng, surf, t0) => { if (!r.pass) exit = 1 - console.log(`${r.pass ? 'PASS' : 'FAIL'} ${s.bundle.padEnd(24)} ${eng.padEnd(9)} ${surf.padEnd(10)} ${r.maps ? r.maps + ' map(s)' : 'no map'}${r.pass ? '' : ' — ' + r.why.join('; ')}`) + console.log(`${r.pass ? 'PASS' : 'FAIL'} ${s.bundle.padEnd(24)} ${eng.padEnd(9)} ${surf.padEnd(10)} ${(r.maps ? r.maps + ' map(s)' : 'no map').padEnd(9)} ${String(Math.round((Date.now() - t0) / 1000)).padStart(3)}s${r.pass ? '' : ' — ' + r.why.join('; ')}`) } - console.log(exit ? 'EXPERIMENTAL-SHELF: FAIL' : `EXPERIMENTAL-SHELF: PASS — every experimental plugin loads on its own page and in the Gallery, under ${ENGINES.join(' and ')}`) + for (const s of shelf) for (const eng of ENGINES) for (const [surf, url, opts] of [ + ['A leaf', leaf(s, eng), {}], + ['B iframe', `${HOST}/B/${s.bundle}/${eng}`, { frame: true }], + ['C script', `${HOST}/C/${s.bundle}/${eng}`, { noMapbox: eng === 'maplibre' }], + ['D gallery', gallery(s, eng), {}], + ].filter(x => SURFACES.includes(x[0][0]))) { + let t0 = Date.now() + const r = await measure(url, s, null, opts) + row(r, s, eng, surf, t0) + // C host-tk: only where the script embed really made a MAPBOX map. A MapLibre-only plugin + // (prayer-walk-planner) takes no token at all, so there is nothing to prove. + if (surf === 'C script' && eng === 'mapbox' && r.pass) { + if (!r.mapboxMaps) { console.log(`n/a ${s.bundle.padEnd(24)} ${eng.padEnd(9)} C host-tk — no Mapbox map on this plugin, it takes no token`); continue } + t0 = Date.now() + row(await measure(`${HOST}/C/${s.bundle}/mapbox/fake`, s, null, { provenanceOnly: true }), s, eng, 'C host-tk', t0) + } + } + console.log(exit ? 'EXPERIMENTAL-SHELF: FAIL' : `EXPERIMENTAL-SHELF: PASS — every experimental plugin works on its own page, iframed and script-embedded on another site, and in the Gallery, under ${ENGINES.join(' and ')}`) } } finally { await b.close(); reap() } process.exit(exit)