From a3e58ac7cadcad4300776c1e08f2ce157e135f65 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 08:18:55 +0000 Subject: [PATCH] Serve the packet text at print time instead of bundling all 11 languages 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 /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). --- internal/check-poster-render-pinned.mjs | 33 +- internal/generate-tree.mjs | 5 + library/composables/usePosterPrints.js | 9 +- library/packet/packetStrings.js | 49 + library/packet/poster-render.integrity.js | 4 + library/packet/posterSheets.js | 7 +- library/packet/sha384.js | 9 + library/scripts/posters/pin-poster-render.mjs | 7 +- .../my-upg-100-list/upg-100-list.vue | 14 +- public/dx-maps/assets/lib/packet-strings.json | 2728 +++++++++++++++++ 10 files changed, 2847 insertions(+), 18 deletions(-) create mode 100644 library/packet/packetStrings.js create mode 100644 library/packet/sha384.js create mode 100644 public/dx-maps/assets/lib/packet-strings.json diff --git a/internal/check-poster-render-pinned.mjs b/internal/check-poster-render-pinned.mjs index d7c864d60..af175eba2 100644 --- a/internal/check-poster-render-pinned.mjs +++ b/internal/check-poster-render-pinned.mjs @@ -12,7 +12,7 @@ * · a TAMPERED copy is refused, and its code never executes (a planted side effect stays unset); * · the GENUINE copy loads and draws a real sheet. */ -import { readFileSync, writeFileSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs' +import { readFileSync, writeFileSync, mkdtempSync, mkdirSync, rmSync, existsSync } from 'node:fs' import { createHash } from 'node:crypto' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -98,5 +98,36 @@ ok(typeof html === 'string' && html.length > 10000 && / 10000, `run as a classic script it draws the module's sheet byte for byte (${a.length} bytes)`) } +// THE PACKET TEXT (library/packet/packetStrings.js), served at lib/packet-strings.json and pinned. Two-sided: +// the GENUINE served copy loads every language, and a TAMPERED one is refused — the print falls back to the +// bundled English block, so the forged text never reaches a print document. +{ + const { PACKET_STRINGS_SHA384 } = await import(join(REPO, 'library/packet/poster-render.integrity.js')) + const stringsSrc = readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8') + const servedStrings = join(REPO, 'public/dx-maps/assets/lib/packet-strings.json') + ok(PACKET_STRINGS_SHA384 === pinOf(stringsSrc), + `the packet-strings pin matches its source${PACKET_STRINGS_SHA384 === pinOf(stringsSrc) ? '' : ' — run: node library/scripts/posters/pin-poster-render.mjs'}`) + ok(existsSync(servedStrings) && pinOf(readFileSync(servedStrings, 'utf8')) === pinOf(stringsSrc), + 'the served copy (assets/lib/packet-strings.json) is the pinned file') + const { loadPacketStrings } = await import(join(REPO, 'library/packet/packetStrings.js')) + const genuine = await loadPacketStrings(pathToFileURL(join(REPO, 'public/dx-maps/assets')).href) + const langs = Object.keys(genuine).filter((k) => k !== '_meta') + ok(langs.length === 11 && genuine.zh?.zone_affinity_A017 === JSON.parse(stringsSrc).zh.zone_affinity_A017, + `the genuine served copy loads all ${langs.length} languages`) + const tmp3 = mkdtempSync(join(tmpdir(), 'strings-pin-')) + const warn = console.warn; console.warn = () => {} + try { + mkdirSync(join(tmp3, 'lib'), { recursive: true }) + const forged = JSON.parse(stringsSrc); forged.zh.zone_affinity_A017 = 'FORGED' + writeFileSync(join(tmp3, 'lib/packet-strings.json'), JSON.stringify(forged, null, 2) + '\n') + const tampered = await loadPacketStrings(pathToFileURL(tmp3).href) + ok(!JSON.stringify(tampered).includes('FORGED') && Object.keys(tampered).join() === 'en' + && tampered.en.zone_affinity_A017 === JSON.parse(stringsSrc).en.zone_affinity_A017, + 'a tampered packet-strings table is refused — the print falls back to the bundled English block') + const absent = await loadPacketStrings(pathToFileURL(join(tmp3, 'nowhere')).href) + ok(Object.keys(absent).join() === 'en', 'an unserved table falls back to English rather than failing the print') + } finally { console.warn = warn; rmSync(tmp3, { recursive: true, force: true }) } +} + if (fails) { console.log(`POSTER-RENDER-PINNED: FAIL (${fails})`); process.exit(1) } console.log('POSTER-RENDER-PINNED: PASS — the served generator and places table are the pinned ones, a tampered generator is refused unrun, a tampered table is not used, and the genuine copies draw') diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs index eb6f6e438..935eba149 100644 --- a/internal/generate-tree.mjs +++ b/internal/generate-tree.mjs @@ -1722,6 +1722,10 @@ function main() { //