A single-file build inlines every import, dynamic ones included, so packet-strings.json (~115 KB minified: every language's prayer points, captions and headings) rode in every bundle that can print a packet, list or card deck. With the wall's print messages translated, country-posters crossed the 1.5 MB ceiling (1,502,226 bytes). The table is now served at <assets>/lib/packet-strings.json and fetched at the first print by library/packet/packetStrings.js, the same move as the sheet generator and ne-places.json: - Pinned like them: PACKET_STRINGS_SHA384 in poster-render.integrity.js (pin-poster-render.mjs); the text is hashed before it is parsed, and an unmatched table is not used. These strings are laid into the print document, so an unverified copy never reaches it. - English is bundled as the fallback (a named import of the JSON, so only the en block ships): with no asset base, on file://, on a network error or an integrity mismatch the print comes out in English and the console says why. A print never fails for want of this file. - generate-tree copies it into the pool AND the served allowlist; verified by regenerating the tree (the served copy is byte-identical to source). - check:poster-render-pinned, two-sided: the pin matches source and served copy, the genuine copy loads all 11 languages, a tampered copy falls back to English (mutation-tested: disabling the hash check fails it), and an unserved one falls back rather than failing. - sha384 is one shared helper (library/packet/sha384.js). Sizes: country-posters 1,378,463 (was 1,484,053), my-upg-100-list 1,143,456 (was 1,243,509), regions-dashboard 973,802 (was 1,079,448).
50 lines
3.7 KiB
JavaScript
50 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// pin-poster-render.mjs — write library/packet/poster-render.integrity.js, the sha384s the browser checks
|
|
// before it runs the served map-sheet generator or uses the served capitals-and-cities table. Run it
|
|
// whenever library/packet/poster-render.js or library/scripts/posters/data/ne-places.json changes;
|
|
// check:poster-render-pinned fails (and names this command) until the pins match the files again.
|
|
//
|
|
// node library/scripts/posters/pin-poster-render.mjs
|
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
import { createHash } from 'node:crypto'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { join, dirname } from 'node:path'
|
|
|
|
const LIB = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'packet')
|
|
export const pinOf = (text) => `sha384-${createHash('sha384').update(text, 'utf8').digest('base64')}`
|
|
|
|
// The CLASSIC-SCRIPT copy of the generator, for pages whose Content-Security-Policy forbids blob: scripts:
|
|
// loaded with <script integrity=…> the browser verifies it natively. poster-render.js has one export and
|
|
// no imports, so the transform is mechanical — strict mode kept (modules are strict), the one export
|
|
// becomes window.DoxaPosterRender. generate-tree writes it with THIS function, so the served copy and the
|
|
// pin cannot disagree about what it is.
|
|
export function globalVariant(source) {
|
|
const body = String(source).replace(/^export function createPosterGenerator\(/m, 'function createPosterGenerator(')
|
|
if (body === String(source)) throw new Error('poster-render.js no longer exports createPosterGenerator as the transform expects')
|
|
return `(function () {\n'use strict';\n${body}\nglobalThis.DoxaPosterRender = { createPosterGenerator };\n})();\n`
|
|
}
|
|
|
|
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]
|
|
if (isMain) {
|
|
const pin = pinOf(readFileSync(join(LIB, 'poster-render.js'), 'utf8'))
|
|
const places = pinOf(readFileSync(join(LIB, '..', 'scripts', 'posters', 'data', 'ne-places.json'), 'utf8'))
|
|
const classic = pinOf(globalVariant(readFileSync(join(LIB, 'poster-render.js'), 'utf8')))
|
|
const strings = pinOf(readFileSync(join(LIB, '..', 'scripts', 'posters', 'packet-strings.json'), 'utf8'))
|
|
writeFileSync(join(LIB, 'poster-render.integrity.js'), `// GENERATED by library/scripts/posters/pin-poster-render.mjs — do not edit by hand.
|
|
// The sha384 of library/packet/poster-render.js. The browser fetches the served copy of that file
|
|
// (<assets>/lib/poster-render.js), hashes it, and runs it only when it matches this pin: code loaded at
|
|
// print time is integrity-pinned like every embed, because it executes on partner pages.
|
|
export const POSTER_RENDER_SHA384 = '${pin}'
|
|
// The sha384 of library/scripts/posters/data/ne-places.json, served at <assets>/boundaries/ne-places.json.
|
|
// Data, not code, but pinned the same way: a sheet uses the places only when they are the pinned ones.
|
|
export const NE_PLACES_SHA384 = '${places}'
|
|
// The sha384 of the classic-script copy (<assets>/lib/poster-render.global.js, globalVariant of the file above),
|
|
// loaded with <script integrity> where a page's policy forbids importing a blob: of the verified text.
|
|
export const POSTER_RENDER_GLOBAL_SHA384 = '${classic}'
|
|
// The sha384 of library/scripts/posters/packet-strings.json, served at <assets>/lib/packet-strings.json. The
|
|
// packet, list and card text in every language, fetched at print time (packet/packetStrings.js) and used only
|
|
// when it matches; otherwise the print falls back to the bundled English block.
|
|
export const PACKET_STRINGS_SHA384 = '${strings}'
|
|
`)
|
|
console.log(`[pin-poster-render] ${pin}\n[pin-poster-render] ne-places ${places}\n[pin-poster-render] classic ${classic}\n[pin-poster-render] packet-strings ${strings}`)
|
|
}
|