Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person, named fleet agents or carried private host names now state the same technical fact in neutral words: poster-render (including the comments it writes into generated posters), packet-render, the poster and research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/), which nothing reads, are removed.
62 lines
2.9 KiB
JavaScript
62 lines
2.9 KiB
JavaScript
/**
|
|
* loadPinnedUmd — load a PINNED, integrity-checked UMD library and capture it WITHOUT touching the host
|
|
* page's global of the same name. One copy of what pplr-data-maps (60faff1a) and the Wall carried twice.
|
|
*
|
|
* WHY THE TRAP (measured on PPLR): a UMD build such as d3's writes its exports INTO an existing
|
|
* global — `(t = globalThis).d3 = t.d3 || {}` — so a host that owns a d3 had ours written over it (577
|
|
* added keys, version '7.9.0' on the host's object) even though we "put it back" afterwards. So while OUR
|
|
* script executes (document.currentScript === the tag), `window[globalName]` reads and writes a private
|
|
* slot; any other code, at every moment, reads and writes the host's own value. A non-configurable
|
|
* `var` global cannot be trapped: it is emptied for the load and restored after.
|
|
*
|
|
* @param {{url: string, integrity: string}} pin an entry of library/constants/cdn.js
|
|
* @param {string} globalName the name the UMD assigns (e.g. 'd3')
|
|
* @returns {Promise<any>} the library, cached per url; rejects on network/integrity failure
|
|
*/
|
|
const cache = new Map()
|
|
|
|
export function loadPinnedUmd(pin, globalName) {
|
|
if (typeof window === 'undefined' || typeof document === 'undefined') return Promise.reject(new Error('no window'))
|
|
if (cache.has(pin.url)) return cache.get(pin.url)
|
|
const p = new Promise((resolve, reject) => {
|
|
const s = document.createElement('script')
|
|
s.src = pin.url
|
|
s.integrity = pin.integrity
|
|
s.crossOrigin = 'anonymous'
|
|
const own = Object.getOwnPropertyDescriptor(window, globalName)
|
|
let hostValue = window[globalName]
|
|
let ours
|
|
let trapped = false
|
|
if (!own || (own.configurable && 'value' in own)) {
|
|
Object.defineProperty(window, globalName, {
|
|
configurable: true, enumerable: own ? own.enumerable : true,
|
|
get: () => (document.currentScript === s ? ours : hostValue),
|
|
set: (v) => { if (document.currentScript === s) ours = v; else hostValue = v },
|
|
})
|
|
trapped = true
|
|
} else if (own.writable) {
|
|
window[globalName] = undefined
|
|
}
|
|
const restore = () => {
|
|
if (trapped) {
|
|
delete window[globalName]
|
|
if (own || hostValue !== undefined) {
|
|
Object.defineProperty(window, globalName, { value: hostValue, writable: own ? own.writable : true,
|
|
configurable: true, enumerable: own ? own.enumerable : true })
|
|
}
|
|
} else if (own && own.writable) {
|
|
window[globalName] = hostValue
|
|
}
|
|
}
|
|
s.onload = () => {
|
|
const got = trapped ? ours : window[globalName]
|
|
restore()
|
|
if (got) resolve(got)
|
|
else { cache.delete(pin.url); reject(new Error(`${globalName} loaded but exported nothing (an AMD loader on the page?)`)) }
|
|
}
|
|
s.onerror = () => { restore(); cache.delete(pin.url); reject(new Error(`${globalName} failed to load (network or integrity)`)) }
|
|
document.head.appendChild(s)
|
|
})
|
|
cache.set(pin.url, p)
|
|
return p
|
|
}
|