Composites: give a partner's embed the token it passes in
A JS embed of either composite mounted a map that could not draw. MEASURED the
way a partner actually embeds — one origin serving the tree with CORS, a
SEPARATE origin serving the page:
research-map-plus-grid "No Mapbox access token provided", style 0, tiles 0, no canvas
dashboard-plus-grid the same
(iframe embeds were fine throughout: style 2, tiles 9, 0 failures)
Both composites declared NO PROPS, so the host's profile-config — token included
— was read by nobody. Our own leaves hide this completely: a leaf carries a
BAKED token and assigns window.MAP_TOKEN, which the shared-token bridge hands to
the nested map. The failure therefore exists ONLY where there is no leaf, which
is precisely the partner page the embed snippet is written for. It is also the
snippet we publish: profile-config='{"tk":"YOUR_MAPBOX_TOKEN"}' is the documented
way to supply a key, and it did nothing.
Both now parse their host config, publish the token page-wide through the shared
composable, and hand it to their children. The two differ in how children are
mounted, so the last step differs:
research-map-plus-grid composes by TAG + BUNDLE URL, so the token is threaded
into the child element's profile-config, where its own ProfileLoader reads it.
dashboard-plus-grid bakes its children in and bypasses ProfileLoader entirely
— and ProfileLoader is the only provider of `mapboxToken` in the repo, so
DashboardMapPanel's inject fell through to its empty default. It now provides
`mapboxToken` itself, from the host's tk with the shared page token behind it.
Spread last and only when present, so a host that supplies none leaves the key
absent and the child asks for exactly what its standalone leaf asks for. A
malformed attribute is caught and ignored: the map half is more useful
token-less than the page is with a thrown setup().
This went in the PLUGINS, not the library. A shared change reaches every map —
that is the lesson SHOW_EXPORT_BUTTON taught this week, and nothing here needs
to be shared to work.
MEASURED AFTER, same harness: research-map-plus-grid canvas 1280x906, style 2,
tiles 9, 0 failures; dashboard-plus-grid canvas present, style 2, tiles 10, 0
failures — basemap, pins, legend, table and charts all drawn. Screenshots read.
Both LEAVES re-checked for regression: canvas 1, 0 failed requests, each still
painting its own map.
This commit is contained in:
parent
f344b74334
commit
dc252bb304
10 changed files with 126 additions and 52 deletions
|
|
@ -13,8 +13,9 @@
|
|||
* Same "import the REAL components, no fork" discipline as big-picture: the grid is the exact
|
||||
* people-groups-grid component; the dashboard is the exact research-dashboard component.
|
||||
*/
|
||||
import { reactive, provide, ref } from 'vue'
|
||||
import { reactive, provide, ref, computed } from 'vue'
|
||||
import { resolveEmbedBase } from '@map/utils/embedBase.js'
|
||||
import { useSharedMapToken, publishMapToken } from '@map/composables/useSharedMapToken.js'
|
||||
import StackLayout from '@map/layouts/StackLayout.vue'
|
||||
import ResearchDashboard from '../research-dashboard/research-dashboard.vue'
|
||||
import PeopleGroupsGrid from '../people-groups-grid/people-groups-grid.vue'
|
||||
|
|
@ -217,13 +218,49 @@ function onDashboardFilter({ filterSet = {}, searchTerm = '' } = {}) {
|
|||
// Note this is exactly the two-host defect class: research-dashboard standalone PASSES the same
|
||||
// gate (1/1, verified this tick), and the identical component inside this composite FAILED. Only a
|
||||
// gate that drives BOTH hosts can tell those apart, which is why the composite is now a target.
|
||||
// ★ THE HOST'S OWN profile-config, WHICH THIS COMPOSITE USED TO THROW AWAY.
|
||||
// It declared no props, so a partner's <dashboard-plus-grid profile-config='{"tk":"..."}'> lost its
|
||||
// token entirely. MEASURED on a third-party origin before this: 'No Mapbox access token provided',
|
||||
// 0 style and 0 tile requests to api.mapbox.com, no canvas — the JS embed, one of the two shipped
|
||||
// embed methods, mounted a dashboard whose map could not draw. Our own leaf hides it, because that
|
||||
// page carries a BAKED token; the failure exists only where there is no leaf, which is exactly the
|
||||
// partner case. Same defect and same shape as research-map-plus-grid.
|
||||
const props = defineProps({
|
||||
/** The host's JSON config, exactly as the embed snippet writes it. */
|
||||
profileConfig: { type: [String, Object], default: null },
|
||||
})
|
||||
const hostConfig = computed(() => {
|
||||
const raw = props.profileConfig
|
||||
if (!raw) return {}
|
||||
if (typeof raw === 'object') return raw
|
||||
// A malformed attribute must not take the element down with it.
|
||||
try { return JSON.parse(raw) } catch { return {} }
|
||||
})
|
||||
const hostToken = computed(() => {
|
||||
const tk = hostConfig.value?.tk
|
||||
return typeof tk === 'string' && tk ? tk : ''
|
||||
})
|
||||
if (hostToken.value) publishMapToken(hostToken.value)
|
||||
|
||||
// ★ AND PROVIDE IT THE WAY ProfileLoader WOULD. This composite bakes its children in rather than
|
||||
// mounting them through ProfileLoader, and ProfileLoader is the ONLY provider of 'mapboxToken' in
|
||||
// the repo — so DashboardMapPanel's inject('mapboxToken', ref('')) fell through to its empty
|
||||
// default here. The shared page token keeps a late-arriving token working (the leaf FETCHES one),
|
||||
// which is what the composable exists for; the host's own tk still wins.
|
||||
const sharedToken = useSharedMapToken()
|
||||
const mapboxToken = computed(() => hostToken.value || sharedToken.value || '')
|
||||
provide('mapboxToken', mapboxToken)
|
||||
|
||||
const SHARE_LEAF = 'dashboard-plus-grid/dashboard-plus-grid/index.html'
|
||||
const shareBase = resolveEmbedBase(null)
|
||||
provide('profileConfig', ref({
|
||||
provide('profileConfig', computed(() => ({
|
||||
profile: 'research-dashboard',
|
||||
layout: 'page-scroll',
|
||||
shareUrl: (shareBase ? shareBase.replace(/\/+$/, '') + '/' : '') + SHARE_LEAF,
|
||||
}))
|
||||
// Spread last so a host that supplies none leaves the key absent, and never overwrite the two
|
||||
// settings above — those are this composite's own layout and share decisions, not the host's.
|
||||
...(hostToken.value ? { tk: hostToken.value } : {}),
|
||||
})))
|
||||
|
||||
const sections = [
|
||||
// The research-dashboard leaf SELF-TITLES (its own DashboardTopBar renders a "Research Dashboard"
|
||||
|
|
|
|||
|
|
@ -55,8 +55,40 @@
|
|||
* ON via prop. This is why the swap-inside-one-element idea was genuinely complex: the two top
|
||||
* elements do not merely look different, they imply OPPOSITE control ownership below.
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import StackLayout from '@map/layouts/StackLayout.vue'
|
||||
import { resolveEmbedBase } from '@map/utils/embedBase.js'
|
||||
import { publishMapToken } from '@map/composables/useSharedMapToken.js'
|
||||
|
||||
// ★ THE HOST'S OWN profile-config, WHICH THIS COMPOSITE USED TO THROW AWAY.
|
||||
// It declared no props at all, so a partner's <research-map-plus-grid profile-config='{"tk":"..."}'>
|
||||
// had its token dropped on the floor. On our own generated leaf that was invisible: the leaf page
|
||||
// carries a BAKED token, assigns window.MAP_TOKEN, and the shared-token bridge hands it to the
|
||||
// nested map. A partner page has no leaf and no baked token, so the JS embed — one of the two
|
||||
// shipped embed methods — mounted a map that could not draw. MEASURED on a third-party origin
|
||||
// before this: 'No Mapbox access token provided', 0 style and 0 tile requests, no canvas.
|
||||
const props = defineProps({
|
||||
/** The host's JSON config, exactly as the embed snippet writes it. */
|
||||
profileConfig: { type: [String, Object], default: null },
|
||||
})
|
||||
|
||||
const hostConfig = computed(() => {
|
||||
const raw = props.profileConfig
|
||||
if (!raw) return {}
|
||||
if (typeof raw === 'object') return raw
|
||||
// A malformed attribute must not take the whole element down with it — the map half is more
|
||||
// useful token-less than the page is with a thrown setup().
|
||||
try { return JSON.parse(raw) } catch { return {} }
|
||||
})
|
||||
|
||||
// Publish page-wide as well as threading it below. Threading covers THIS child deterministically;
|
||||
// publishing covers any element on the page that mounted before the attribute was read, which is
|
||||
// the shape the shared-token composable exists for.
|
||||
const hostToken = computed(() => {
|
||||
const tk = hostConfig.value?.tk
|
||||
return typeof tk === 'string' && tk ? tk : ''
|
||||
})
|
||||
if (hostToken.value) publishMapToken(hostToken.value)
|
||||
|
||||
// The absolute url this composite is shared and iframed as. Built here rather than written as a
|
||||
// literal because resolveEmbedBase() walks profileConfig.embedBaseUrl → window.MAP_EMBED_BASE_URL →
|
||||
|
|
@ -103,7 +135,7 @@ const MAP_H = '88svh'
|
|||
const bundleSrc = (bundle, file) =>
|
||||
import.meta.env.DEV ? `/plugin-designs/${bundle}/index.js` : `../../${bundle}/${file}.js`
|
||||
|
||||
const sections = [
|
||||
const sections = computed(() => [
|
||||
// No section title: the research map self-titles through its own chrome, so a StackLayout title
|
||||
// here would render the name twice — the duplicate-header bug the Driver rejected on the desktop
|
||||
// sibling (BUG-RESEARCH-DASHBOARD-HEADER-STACK-WRONG). One header per element.
|
||||
|
|
@ -155,6 +187,11 @@ const sections = [
|
|||
// reader is scanning, a fly-tour that moves the camera underneath fights the other half.
|
||||
// The standalone leaf keeps both; this is a per-host decision, which is why it is config.
|
||||
legendTools: false,
|
||||
// Thread the host's token through to the child. Spread so a host that supplies none
|
||||
// leaves the key ABSENT rather than empty: ProfileLoader's ladder is `config.tk ||
|
||||
// sharedToken`, and an empty string would be falsy anyway, but an absent key keeps the
|
||||
// child's own config identical to what its standalone leaf sets.
|
||||
...(hostToken.value ? { tk: hostToken.value } : {}),
|
||||
assetsBase: '../../assets',
|
||||
shareUrl: SHARE_URL
|
||||
})
|
||||
|
|
@ -176,7 +213,7 @@ const sections = [
|
|||
src: bundleSrc('people-groups-grid', 'people-groups-grid'),
|
||||
attrs: { 'show-search': 'true', 'show-filters': 'true', 'show-total': 'true' }
|
||||
}
|
||||
]
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-VOkoTUw4VURBoPSbk3YnU3Wc/JuN+ywf1eF9D6RHBI2Be/8N8FlY3WX/2CkNk5NS" crossorigin="anonymous"></script>
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-5rOnLbytqwu85ymi4mkktT8jLTvRLGGxlL34Ws9d8MC+e4RYOJ4ghWzF/sskDvGV" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-VOkoTUw4VURBoPSbk3YnU3Wc/JuN+ywf1eF9D6RHBI2Be/8N8FlY3WX/2CkNk5NS" crossorigin="anonymous"></script>
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-5rOnLbytqwu85ymi4mkktT8jLTvRLGGxlL34Ws9d8MC+e4RYOJ4ghWzF/sskDvGV" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -211,7 +211,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-VOkoTUw4VURBoPSbk3YnU3Wc/JuN+ywf1eF9D6RHBI2Be/8N8FlY3WX/2CkNk5NS",
|
||||
"integrity": "sha384-5rOnLbytqwu85ymi4mkktT8jLTvRLGGxlL34Ws9d8MC+e4RYOJ4ghWzF/sskDvGV",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/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=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-VOkoTUw4VURBoPSbk3YnU3Wc/JuN+ywf1eF9D6RHBI2Be/8N8FlY3WX/2CkNk5NS\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-5rOnLbytqwu85ymi4mkktT8jLTvRLGGxlL34Ws9d8MC+e4RYOJ4ghWzF/sskDvGV\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -957,7 +957,7 @@
|
|||
"bundle": "research-map-plus-grid",
|
||||
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
|
||||
"output": "doxa-maps/research-map-plus-grid/research-map-plus-grid.js",
|
||||
"integrity": "sha384-sOkaMV0UFr2uNXfoJpRvaD+gHD6xFlkmpR7MzEwwFdarYdid8aN9Nj1Y8TqQJuFo",
|
||||
"integrity": "sha384-VsnIKuLA6j6sF1vFVgDwdi7p6MggySss5Q6SiE/jG2vX5YgxLeL1pOfsrK1YGmTg",
|
||||
"elements": [
|
||||
"research-map-plus-grid"
|
||||
],
|
||||
|
|
@ -977,7 +977,7 @@
|
|||
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./research-map-plus-grid/research-map-plus-grid/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=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-sOkaMV0UFr2uNXfoJpRvaD+gHD6xFlkmpR7MzEwwFdarYdid8aN9Nj1Y8TqQJuFo\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-VsnIKuLA6j6sF1vFVgDwdi7p6MggySss5Q6SiE/jG2vX5YgxLeL1pOfsrK1YGmTg\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <research-map-plus-grid> custom element. -->
|
||||
<script src="../../research-map-plus-grid.js" integrity="sha384-sOkaMV0UFr2uNXfoJpRvaD+gHD6xFlkmpR7MzEwwFdarYdid8aN9Nj1Y8TqQJuFo" crossorigin="anonymous"></script>
|
||||
<script src="../../research-map-plus-grid.js" integrity="sha384-VsnIKuLA6j6sF1vFVgDwdi7p6MggySss5Q6SiE/jG2vX5YgxLeL1pOfsrK1YGmTg" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <research-map-plus-grid> custom element. -->
|
||||
<script src="../research-map-plus-grid.js" integrity="sha384-sOkaMV0UFr2uNXfoJpRvaD+gHD6xFlkmpR7MzEwwFdarYdid8aN9Nj1Y8TqQJuFo" crossorigin="anonymous"></script>
|
||||
<script src="../research-map-plus-grid.js" integrity="sha384-VsnIKuLA6j6sF1vFVgDwdi7p6MggySss5Q6SiE/jG2vX5YgxLeL1pOfsrK1YGmTg" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue