wip-auto
This commit is contained in:
parent
8e49ec41d6
commit
3cb89eae52
7 changed files with 205 additions and 28 deletions
|
|
@ -288,7 +288,12 @@ Step by step instead: `bun run build` → `bun run cap:sync` → open `deploymen
|
|||
|
||||
The engine is a **runtime parameter** — same bundle, no rebuild. Resolution order
|
||||
(first defined wins): `profile-config.provider` → `?provider=` in the page URL →
|
||||
`window.MAP_PROVIDER` → default `mapbox`.
|
||||
`window.MAP_PROVIDER` → default `auto`.
|
||||
|
||||
`auto` picks **Mapbox when a token is available** (a `tk` in the profile-config, `window.MAP_TOKEN`,
|
||||
`?tk=`, a token pasted once in the tree, the studio's ⚙ Settings, or a configured token URL) and
|
||||
**MapLibre otherwise** — keyless, with no request to Mapbox. The repo ships no Mapbox token, so a
|
||||
fresh checkout draws every map with MapLibre until you give it one. An explicit `provider` always wins.
|
||||
|
||||
```html
|
||||
<!-- per embed -->
|
||||
|
|
@ -299,8 +304,8 @@ https://<cdn>/dx-maps/<bundle>/<profile>/index.html?provider=maplibre
|
|||
```
|
||||
|
||||
`maplibre` runs token-free on the OpenFreeMap style (the right GL script/CSS is injected
|
||||
automatically); the geocoder search bar keeps working in both engines. `mapbox` (the default)
|
||||
behaves exactly as before — existing embeds never change.
|
||||
automatically); the geocoder search bar keeps working in both engines. `mapbox` behaves exactly
|
||||
as before, and an embed that passes a token keeps getting Mapbox under `auto`.
|
||||
|
||||
> Status: **on `main`.** Generated pages honor `provider:'maplibre'` end-to-end — the page
|
||||
> loads the MapLibre peer instead of Mapbox and **skips the token ladder entirely** (no
|
||||
|
|
|
|||
134
internal/check-auto-engine.mjs
Normal file
134
internal/check-auto-engine.mjs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-auto-engine — the default engine is 'auto': Mapbox WHEN a token is available, MapLibre otherwise.
|
||||
*
|
||||
* WHY (2026-09-25, by project decision). The public repo carries no Mapbox token. With a default of
|
||||
* 'mapbox', a keyless visitor got a map that could not draw and a paste-a-token prompt, and the only
|
||||
* way out was a token in the repo. 'auto' asks the token ladder instead (useMapProvider.js), and the
|
||||
* generated leaf asks the same ladder before it mounts (generate-tree.mjs, PROVIDER).
|
||||
*
|
||||
* THREE CASES, on one map leaf of THIS tree (or --dir), each in a fresh browser context:
|
||||
* 1 NO TOKEN the map draws with MapLibre, makes ZERO requests to any *.mapbox.com host, and
|
||||
* PAINTS (distinct colours on the canvas, not a blank rectangle).
|
||||
* 2 HOST TOKEN the host sets window.MAP_TOKEN before the page runs: the map draws with Mapbox,
|
||||
* and THAT token is the access_token on its requests to Mapbox.
|
||||
* 3 EXPLICIT WINS the same host token, with ?provider=maplibre: MapLibre, zero Mapbox requests.
|
||||
* The token in case 2 is a well-formed FAKE, built at run time; Mapbox requests that carry it are
|
||||
* recorded and aborted, never sent. The engine is read from the canvas that drew
|
||||
* (mapboxgl-canvas / maplibregl-canvas), not from which globals happen to exist.
|
||||
*
|
||||
* USAGE node internal/check-auto-engine.mjs [--dir <tree root>] [--leaf <bundle>/<profile>]
|
||||
* Run through internal/browser-slot.sh.
|
||||
* EXIT 0 all three hold · 1 one does not · 75 not measured.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const argv = process.argv.slice(2)
|
||||
const arg = (k, d) => { const i = argv.indexOf(k); return i >= 0 && argv[i + 1] ? argv[i + 1] : d }
|
||||
const DIR = resolve(arg('--dir', resolve(ROOT, 'public')))
|
||||
const LEAF = arg('--leaf', 'doxa-simple-map/doxa-simple-map')
|
||||
const MIN_COLOURS = 40
|
||||
const MAPBOX_HOST = /(^|\.)mapbox\.com$/i
|
||||
// A fake public token of the right SHAPE (pk.<base64url JSON>.<sig>), so the engine accepts it.
|
||||
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url')
|
||||
const FAKE_TOKEN = ['pk', b64({ u: 'auto-engine-check', a: 'not-a-real-token' }), 'c2lnbmF0dXJl'].join('.')
|
||||
|
||||
if (!existsSync(join(DIR, TREE_NAME, LEAF, 'index.html'))) { console.log(`AUTO-ENGINE: NOT MEASURED — no ${LEAF} under ${DIR}/${TREE_NAME}/`); process.exit(75) }
|
||||
|
||||
const port = await new Promise((ok) => { const s = createServer().listen(0, '127.0.0.1', () => { const n = s.address().port; s.close(() => ok(n)) }) })
|
||||
const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', DIR], { stdio: 'ignore' })
|
||||
const stop = () => { try { srv.kill() } catch { /* gone */ } }
|
||||
let up = false
|
||||
for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/${TREE_NAME}/manifest.json`).then((r) => r.ok).catch(() => false) }
|
||||
if (!up) { stop(); console.log(`AUTO-ENGINE: NOT MEASURED — could not serve ${DIR}`); process.exit(75) }
|
||||
console.log(`AUTO-ENGINE: measuring THIS TREE — ${DIR} (127.0.0.1:${port}), leaf ${LEAF}`)
|
||||
|
||||
const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] })
|
||||
|
||||
async function measure({ hostToken = '', query = '' }) {
|
||||
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } })
|
||||
const page = await ctx.newPage()
|
||||
const mapbox = []
|
||||
// The engine script itself may come from Mapbox's CDN; everything that CARRIES a token is recorded
|
||||
// and aborted, so the fake token never leaves this machine.
|
||||
await page.route((url) => MAPBOX_HOST.test(url.hostname), (route) => {
|
||||
const u = new URL(route.request().url())
|
||||
mapbox.push(u)
|
||||
if (u.searchParams.has('access_token')) return route.abort()
|
||||
return route.continue()
|
||||
})
|
||||
if (hostToken) await page.addInitScript((tk) => { window.MAP_TOKEN = tk }, hostToken)
|
||||
try {
|
||||
await page.goto(`http://127.0.0.1:${port}/${TREE_NAME}/${LEAF}/index.html${query}`, { waitUntil: 'load', timeout: 90000 })
|
||||
const deadline = Date.now() + 45000
|
||||
let engine = ''
|
||||
while (Date.now() < deadline && !engine) {
|
||||
engine = await page.evaluate(() => {
|
||||
const roots = [document]
|
||||
for (const el of document.querySelectorAll('*')) if (el.shadowRoot) roots.push(el.shadowRoot)
|
||||
for (const r of roots) {
|
||||
if (r.querySelector('canvas.maplibregl-canvas')) return 'maplibre'
|
||||
if (r.querySelector('canvas.mapboxgl-canvas')) return 'mapbox'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
if (!engine) await page.waitForTimeout(500)
|
||||
}
|
||||
const prompt = await page.evaluate(() => !!document.getElementById('tkIn'))
|
||||
let colours = 0
|
||||
if (engine === 'maplibre') {
|
||||
await page.waitForTimeout(8000) // tiles
|
||||
// Straight from CDP: page.screenshot waits for web fonts first and timed out on this leaf.
|
||||
const cdp = await ctx.newCDPSession(page)
|
||||
const png = Buffer.from((await cdp.send('Page.captureScreenshot', { format: 'png' })).data, 'base64')
|
||||
colours = await page.evaluate(async (data) => {
|
||||
const img = new Image(); img.src = 'data:image/png;base64,' + data; await img.decode()
|
||||
const c = document.createElement('canvas'); c.width = img.width; c.height = img.height
|
||||
const g = c.getContext('2d'); g.drawImage(img, 0, 0)
|
||||
const d = g.getImageData(0, 0, c.width, c.height).data, seen = new Set()
|
||||
for (let i = 0; i < d.length; i += 4) seen.add(((d[i] >> 3) << 10) | ((d[i + 1] >> 3) << 5) | (d[i + 2] >> 3))
|
||||
return seen.size
|
||||
}, png.toString('base64'))
|
||||
} else {
|
||||
await page.waitForTimeout(3000)
|
||||
}
|
||||
return { engine: engine || 'none', prompt, colours, mapbox }
|
||||
} catch (e) {
|
||||
return { err: String(e.message).split('\n')[0].slice(0, 120), mapbox }
|
||||
} finally { await ctx.close() }
|
||||
}
|
||||
|
||||
const results = []
|
||||
try {
|
||||
results.push({ name: '1 no token', r: await measure({}) })
|
||||
results.push({ name: '2 host token', r: await measure({ hostToken: FAKE_TOKEN }) })
|
||||
results.push({ name: '3 explicit maplibre + token', r: await measure({ hostToken: FAKE_TOKEN, query: '?provider=maplibre' }) })
|
||||
} finally { await browser.close(); stop() }
|
||||
|
||||
let bad = 0, unmeasured = 0
|
||||
const say = (ok, name, msg) => { console.log(` ${ok ? 'ok ' : 'FAIL'} ${name.padEnd(30)} ${msg}`); if (!ok) bad++ }
|
||||
for (const { name, r } of results) {
|
||||
if (r.err) { console.log(` ?? ${name.padEnd(30)} NOT MEASURED — ${r.err}`); unmeasured++; continue }
|
||||
const withToken = r.mapbox.filter((u) => u.searchParams.has('access_token'))
|
||||
const summary = `engine ${r.engine}, ${r.mapbox.length} Mapbox request(s)${r.prompt ? ', TOKEN PROMPT shown' : ''}`
|
||||
if (name.startsWith('1')) {
|
||||
say(r.engine === 'maplibre' && r.mapbox.length === 0 && r.colours >= MIN_COLOURS && !r.prompt, name,
|
||||
`${summary}, ${r.colours} colours (floor ${MIN_COLOURS}) — want maplibre, 0 Mapbox requests, painted, no prompt`)
|
||||
} else if (name.startsWith('2')) {
|
||||
const carries = withToken.length > 0 && withToken.every((u) => u.searchParams.get('access_token') === FAKE_TOKEN)
|
||||
say(r.engine === 'mapbox' && carries, name,
|
||||
`${summary}, ${withToken.length} carry a token${withToken.length && !carries ? ' — NOT the host token' : ''} — want mapbox, the host token on its requests`)
|
||||
} else {
|
||||
say(r.engine === 'maplibre' && r.mapbox.length === 0, name, `${summary} — want maplibre, 0 Mapbox requests`)
|
||||
}
|
||||
}
|
||||
if (bad) { console.log(`AUTO-ENGINE: FAIL — ${bad} of 3 case(s) do not hold.`); process.exit(1) }
|
||||
if (unmeasured) { console.log(`AUTO-ENGINE: NOT MEASURED — ${unmeasured} case(s) could not be judged.`); process.exit(75) }
|
||||
console.log('AUTO-ENGINE: PASS — no token draws MapLibre keyless; a host token draws Mapbox with that token; an explicit provider wins.')
|
||||
|
|
@ -801,7 +801,7 @@ function mapPageHtml(opts) {
|
|||
geocoder css, mapbox-gl.js and the geocoder js from Mapbox's CDN, chosen at BUILD time —
|
||||
so a partner embedding it with ?provider=maplibre still sent every visitor's IP to Mapbox
|
||||
(4 requests per leaf, measured). The provider is chosen at RUNTIME (profile-config.provider
|
||||
→ ?provider= → window.MAP_PROVIDER → mapbox), and the element's own runtime loaders load
|
||||
→ ?provider= → window.MAP_PROVIDER → auto), and the element's own runtime loaders load
|
||||
exactly that engine: ensureEngineScript / ensureEngineCSS (useMapInstance) and the
|
||||
geocoder's on-demand loader — the path composite leaves already run on. Gate:
|
||||
check:leaf-engine-per-provider (0 Mapbox requests under maplibre; Mapbox unchanged). -->`
|
||||
|
|
@ -868,11 +868,8 @@ ${apiScript}
|
|||
var BASE_CONFIG = ${JSON.stringify(pageConfig)};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// …and a Mapbox leaf opened with provider=maplibre (config, ?provider= or window.MAP_PROVIDER —
|
||||
// the same ladder as resolveMapProvider) needs no Mapbox token either: without this, the public
|
||||
// copy (no baked token) asked a MapLibre visitor to paste a Mapbox token it would never use.
|
||||
var RUNTIME_PROVIDER = String((BASE_CONFIG && BASE_CONFIG.provider) || new URLSearchParams(location.search).get('provider') || window.MAP_PROVIDER || 'mapbox').toLowerCase();
|
||||
var TOKEN_FREE = ${JSON.stringify(tokenFree)} || RUNTIME_PROVIDER === 'maplibre';
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = ${JSON.stringify(tokenFree)};
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
|
|
@ -882,6 +879,8 @@ ${apiScript}
|
|||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
|
|
@ -949,13 +948,24 @@ ${apiScript}
|
|||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins.
|
||||
// 'auto' = Mapbox when a token is available (a rung above, a host-set window.MAP_TOKEN, or a token
|
||||
// endpoint to ask), otherwise MapLibre, keyless: no token prompt, no request to Mapbox. The
|
||||
// element decides the same way from the token this page hands it, so the two cannot disagree.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
if (NESTS_MAPS && !directToken) {
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
|
|
@ -986,7 +996,9 @@ ${apiScript}
|
|||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
|
|
@ -1276,10 +1288,11 @@ function scanBundles() {
|
|||
// <script src>, and a preload is only reused when its request mode matches — with crossorigin it
|
||||
// would download twice. Mapbox only: a non-map profile gets none, and a partner switching to
|
||||
// provider:"maplibre" is told to drop the line, since that engine must make zero Mapbox requests.
|
||||
// The default engine is 'auto' (Mapbox only with a token), so a partner with no token drops it too.
|
||||
embed: {
|
||||
iframe: iframeEmbed,
|
||||
script: isMap && MAPBOX_GL_JS
|
||||
? `<!-- Mapbox engine, fetched early; remove this line if you set "provider":"maplibre" -->\n<link rel="preload" as="script" href="${MAPBOX_GL_JS}">\n` + scriptEmbed
|
||||
? `<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in "tk": without one (or with "provider":"maplibre") the map draws with MapLibre and this line must go -->\n<link rel="preload" as="script" href="${MAPBOX_GL_JS}">\n` + scriptEmbed
|
||||
: scriptEmbed,
|
||||
},
|
||||
...(instances.length ? { instances } : {}),
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ export function useMapInstance({
|
|||
// 'style.load') cannot do — see useMapTheme.bootConfig. Styles without imports ignore it;
|
||||
// NOT forwarded to MapLibre. Default undefined → byte-for-byte the previous behaviour.
|
||||
config = undefined,
|
||||
// provider: 'mapbox' (default) | 'maplibre'. Selects the GL engine.
|
||||
// provider: 'mapbox' | 'maplibre' | unset ('auto', see useMapProvider.js). Selects the GL engine.
|
||||
//
|
||||
// ★ LEFT UNSET, THE PAGE DECIDES — THAT IS WHAT MAKES ONE SWITCH FLIP EVERY MAP.
|
||||
// This used to default to the LITERAL 'mapbox'. resolveMapProvider has long supported a
|
||||
|
|
@ -296,7 +296,8 @@ export function useMapInstance({
|
|||
// Resolving here instead moves the decision to the one place every map already goes through, so
|
||||
// a partner running thousands of embeds sets ONE value and the whole tree follows.
|
||||
// An explicit caller still wins outright (prayer-walk-planner pins 'maplibre' deliberately), and
|
||||
// with no page switch set resolveMapProvider returns 'mapbox' — so the default is unchanged.
|
||||
// With no switch set, resolveMapProvider decides 'auto': Mapbox when a token is available (this
|
||||
// call's accessToken counts), MapLibre when none is.
|
||||
provider = undefined,
|
||||
center = [20, 10],
|
||||
zoom = 2,
|
||||
|
|
@ -363,7 +364,7 @@ export function useMapInstance({
|
|||
// degrades to the page switch instead of throwing.
|
||||
let _pcForProvider = null
|
||||
try { _pcForProvider = inject('profileConfig', null) } catch { /* called outside setup */ }
|
||||
provider = provider || resolveMapProvider(_pcForProvider)
|
||||
provider = provider || resolveMapProvider(_pcForProvider, { token: accessToken })
|
||||
const isMaplibre = provider === 'maplibre'
|
||||
|
||||
// THE READER'S LANGUAGE, for the basemap's place names (maps/localize-labels.js). ProfileLoader
|
||||
|
|
|
|||
|
|
@ -6,20 +6,29 @@
|
|||
* 2. ?provider= in the page URL — read here directly, so any generated leaf page can be
|
||||
* flipped from the URL without the host doing anything
|
||||
* 3. window.MAP_PROVIDER — host global, for a host that sets it programmatically
|
||||
* 4. 'mapbox' — default; existing behaviour, never regresses
|
||||
* 4. 'auto' — the default: Mapbox WHEN A TOKEN IS AVAILABLE, else MapLibre
|
||||
*
|
||||
* Valid values: 'mapbox' | 'maplibre'. Anything else falls back to the default.
|
||||
* Valid values: 'mapbox' | 'maplibre' | 'auto'. Anything else is treated as 'auto'.
|
||||
*
|
||||
* ★ This header previously listed only THREE rungs, omitting ?provider= and numbering
|
||||
* window.MAP_PROVIDER as step 2 — it described a ladder the function does not implement.
|
||||
* documentation/how-to.md ("Swap the map engine") carries the correct four-rung order; keep the two
|
||||
* in sync, and treat THIS function as the authority when they disagree.
|
||||
* ★ 'auto' (2026-09-25, by project decision). The public repo carries NO Mapbox token, so a default
|
||||
* of 'mapbox' gave every keyless visitor a map that could not draw and a paste-a-token prompt.
|
||||
* 'auto' asks the token ladder instead: a token in this element's profile-config (`tk`), or one
|
||||
* published page-wide (window.MAP_TOKEN — the generated leaf's ?tk= / pasted / baked / fetched rungs,
|
||||
* the studio's ⚙ Settings, and any element handed a `tk` all land there, see useSharedMapToken)
|
||||
* means Mapbox, exactly as before. No token anywhere means MapLibre, keyless, with zero requests to
|
||||
* Mapbox. An explicit provider (rungs 1-3) always wins.
|
||||
* The decision is taken when the map is created: a token that arrives LATER does not swap a map
|
||||
* that already drew with MapLibre (a host that fetches its token should set it before mounting, or
|
||||
* set provider:'mapbox' explicitly).
|
||||
*
|
||||
* documentation/how-to.md ("Swap the map engine") carries the same order; keep the two in sync, and
|
||||
* treat THIS function as the authority when they disagree.
|
||||
*/
|
||||
|
||||
import { inject } from 'vue'
|
||||
|
||||
export const MAP_PROVIDERS = ['mapbox', 'maplibre']
|
||||
export const DEFAULT_PROVIDER = 'mapbox'
|
||||
export const DEFAULT_PROVIDER = 'auto'
|
||||
|
||||
/** Unwrap a ref/computed to its config object; pass plain objects through. */
|
||||
function unwrapConfig(profileConfig) {
|
||||
|
|
@ -30,18 +39,33 @@ function unwrapConfig(profileConfig) {
|
|||
return profileConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a Mapbox token available to this map? The element's own `tk`, or the page-wide token
|
||||
* (window.MAP_TOKEN, which useSharedMapToken bridges to every element's published tk).
|
||||
*/
|
||||
export function hasMapToken(cfg) {
|
||||
// The embed snippets ship tk:"YOUR_MAPBOX_TOKEN" as a placeholder; an unfilled one is not a token.
|
||||
const real = (v) => typeof v === 'string' && !!v.trim() && !/^YOUR_/.test(v.trim())
|
||||
if (real(cfg?.tk)) return true
|
||||
if (typeof window === 'undefined') return false
|
||||
return real(window.MAP_TOKEN) || real(window.__doxaMapToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the provider from a profile-config (object or ref) + window fallback.
|
||||
* Always returns a concrete engine ('mapbox' | 'maplibre'); 'auto' is decided here. `token` is a
|
||||
* token the caller holds outside the config (useMapInstance's accessToken); it counts as available.
|
||||
* Pure — safe to call outside a Vue setup().
|
||||
*/
|
||||
export function resolveMapProvider(profileConfig) {
|
||||
export function resolveMapProvider(profileConfig, { token } = {}) {
|
||||
const cfg = unwrapConfig(profileConfig)
|
||||
const fromConfig = cfg?.provider
|
||||
const fromUrl = typeof location !== 'undefined'
|
||||
? new URLSearchParams(location.search).get('provider') : undefined
|
||||
const fromWindow = typeof window !== 'undefined' ? window.MAP_PROVIDER : undefined
|
||||
const raw = String(fromConfig || fromUrl || fromWindow || DEFAULT_PROVIDER).toLowerCase().trim()
|
||||
return MAP_PROVIDERS.includes(raw) ? raw : DEFAULT_PROVIDER
|
||||
if (MAP_PROVIDERS.includes(raw)) return raw
|
||||
return hasMapToken(cfg) || hasMapToken({ tk: token }) ? 'mapbox' : 'maplibre'
|
||||
}
|
||||
|
||||
/** Human-readable engine label for a badge / UI. */
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ const mapContainer = ref(null)
|
|||
// 1. profile-config.provider — this embed, decided by whoever mounted the element
|
||||
// 2. ?provider= / window.MAP_PROVIDER — the page, i.e. "swap every map on this site"
|
||||
// 3. dashboard.config.js — this bundle's shipped default (Mapbox, by the ruling above)
|
||||
// With no switch set, resolveMapProvider returns 'mapbox' and step 2 cannot fire, so the shipped
|
||||
// behaviour is byte-for-byte unchanged.
|
||||
// With no switch set, resolveMapProvider decides 'auto': with a token step 2 does not fire and the
|
||||
// shipped default holds; with NO token it answers 'maplibre', because Mapbox cannot draw without one.
|
||||
const mapProvider = computed(() => {
|
||||
const fromElement = profileConfig?.value?.provider
|
||||
if (fromElement === 'maplibre' || fromElement === 'mapbox') return fromElement
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"embedBaseUrl": "",
|
||||
"assetsBaseUrl": "",
|
||||
"tokenUrl": "",
|
||||
"token": "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ",
|
||||
"token": "",
|
||||
"dropin": {
|
||||
"dest": "../doxa-life/public/embed/doxa-maps-build",
|
||||
"include": [
|
||||
|
|
|
|||
Loading…
Reference in a new issue