Pin popups ask the map which engine built it, instead of assuming Mapbox

RegionCountryMap built its popup from window.mapboxgl and returned SILENTLY when
that global was absent — the worst shape of failure, because a pin click would
simply do nothing and no error would appear. It has been passing only because
every generated leaf <head> statically loads mapbox-gl, so under provider=maplibre
a MapLibre map was opening a MAPBOX popup. studio is removing those static tags,
since a Mapbox script leaks the visitor's IP to Mapbox even for a partner who
deliberately chose MapLibre; the moment that lands, these popups vanish.

glFor(map) asks which namespace owns the map instance. It checks MapLibre FIRST
with an instanceof test, because a page can legitimately carry both globals and
only the instance knows which one made it.

The controls read is fixed with it. It was `window.mapboxgl || window.maplibregl`,
which happened to work through its fallback but preferred Mapbox even on a
MapLibre map — the identical bug one line away from the same silent failure.

The proof fails first by construction. The probe BLOCKS the Mapbox script, then
asserts window.mapboxgl is genuinely undefined BEFORE clicking, so the pass that
follows is a pass of the thing that used to break: with no peer, MapLibre present,
a pin click opens "Deaf Liechtensteiners … Population 400"; under Mapbox it opens
"Vinza … Population 33,000", unchanged.

glFor is copied, not imported: the same function is in another plugin's folder
(pplr-data-maps/src/utils/pplrData.js:64), and importing across plugin folders
would couple two plugins through a path neither owns. library/ is the right home
the moment a third consumer appears, and both copies should die in that commit.
This commit is contained in:
doxa-maps-88655c70 2026-09-25 16:59:57 +00:00
parent 2f204c8e30
commit 52552e7c48
9 changed files with 85 additions and 49 deletions

View file

@ -27,6 +27,7 @@
*/
import { ref, computed, watch, onMounted, onBeforeUnmount, inject } from 'vue'
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { glFor } from './glFor.js'
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
@ -317,7 +318,13 @@ function onPinClick(e) {
emit('pick-country', { code: p.code || '', region: p.region || '', name: p.name || '' })
return
}
const g = window.mapboxgl
// ★ THE ENGINE THAT BUILT THIS MAP, not the Mapbox global. This read `window.mapboxgl` and
// returned SILENTLY when it was absent. Under provider=maplibre it worked only because every
// leaf <head> statically loads mapbox-gl — so a MapLibre map was opening a MAPBOX popup. studio
// is removing those static tags (they leak a visitor's IP to Mapbox for partners who chose
// MapLibre), and the moment that lands this popup would vanish with no error at all: the pin
// click would simply do nothing. glFor() asks which namespace owns the map instance.
const g = glFor(map.value)
if (!g) return
const p = f.properties || {}
const html = popupHtml(p)
@ -594,7 +601,10 @@ onMounted(async () => {
// new mapboxgl.FullscreenControl(), 'top-right'
// Guarded per control so a MapLibre provider, which names these the same but not always
// identically, cannot take the whole map down with it.
const gl = (typeof window !== 'undefined') && (window.mapboxgl || window.maplibregl)
// Same question as the popup: which engine owns THIS map. The old `mapboxgl || maplibregl`
// happened to work because the fallback existed, but it preferred Mapbox even on a MapLibre
// map — the identical shape of bug, one line away from the same silent failure.
const gl = glFor(map.value)
if (gl) {
try { map.value.addControl(new gl.NavigationControl({ showCompass: false }), 'top-right') } catch (e) { console.warn('[RegionCountryMap] NavigationControl unavailable:', e && e.message) }
try { map.value.addControl(new gl.FullscreenControl(), 'top-right') } catch (e) { console.warn('[RegionCountryMap] FullscreenControl unavailable:', e && e.message) }

View file

@ -0,0 +1,26 @@
/**
* The map-engine namespace that built a given map instance — Popup, Marker, NavigationControl.
*
* ★ WHY THIS EXISTS. Every view here was written against the Mapbox global, and under
* provider=maplibre that global does not exist. `new window.mapboxgl.Popup(...)` then throws, or —
* worse, and what this plugin actually did — the code guards with `if (!g) return` and the pin
* click silently does NOTHING. It has been passing only because each generated leaf <head>
* statically loads mapbox-gl, so a MapLibre map was opening a Mapbox popup. Those tags are being
* removed (a static Mapbox script leaks the visitor's IP to Mapbox even for a partner who chose
* MapLibre), so the engine has to be derived from the map, not assumed from a global.
*
* ★ WHY A COPY AND NOT AN IMPORT. The same function lives at
* plugin-designs/pplr-data-maps/src/utils/pplrData.js:64. Importing across plugin folders would
* couple two plugins through a path neither owns, and RULE 2 says default to the plugin folder;
* library/ is the right home the moment a THIRD consumer appears, at which point both copies
* should be deleted in the same commit rather than left to drift.
*
* Order matters: ask MapLibre first with an instanceof test, because a page can legitimately have
* BOTH globals loaded and only the instance knows which one made it.
*/
export function glFor(map) {
if (typeof window === 'undefined') return undefined
const ml = window.maplibregl
if (ml && map && ml.Map && map instanceof ml.Map) return ml
return window.mapboxgl || ml
}

View file

@ -888,7 +888,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8",
"integrity": "sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR",
"elements": [
"regions-dashboard"
],
@ -908,7 +908,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -922,7 +922,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -936,7 +936,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -888,7 +888,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8",
"integrity": "sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR",
"elements": [
"regions-dashboard"
],
@ -908,7 +908,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -922,7 +922,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -936,7 +936,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-TU5ENRNb2AgmYM/RXl6VcnemohobgtaGvQZisBWIK33gtMMGIcor2Fp9ovLNPds8" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-8B/pskEDhffypzL+XSoQ9J9CjREzA9Nspsy5uPcskQt7is+e/F1SxKKJmcro5rjR" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: