Files
meshcore-analyzer/tests/unit/test-vendored-libs.js
T
TeTeHackoandClaude Opus 5.5 684f89f200 feat(frontend): serve Leaflet, leaflet.heat and Chart.js from vendor/, not unpkg.com (#2299)
Refs #1554.

`index.html`, `area-map.html` and `geofilter-builder.html` load Leaflet,
leaflet.heat and Chart.js from **unpkg.com** on every page view. That
causes three problems:

- **Privacy:** every visitor sends a request to a third party.
- **Availability:** where unpkg is blocked (script blockers, filtered
networks) or down, the map and every chart are gone. The observer page
then fails with `Error: Chart is not defined`.
- **#1554:** the CSP target there, `script-src 'self'`, blocks these
scripts just like the inline handlers the issue lists, but they are
missing from its inventory.

## Change

- **Vendored libraries:** `public/vendor/leaflet-1.9.4/` (with
`images/`), `vendor/leaflet-heat-0.2.0.js` and
`vendor/chart-4.5.1.umd.min.js`.
- They are the unpkg dist files byte for byte, verified against the SRI
hashes `index.html` already pins.
- The two standalone pages loaded Leaflet without SRI; they now carry
it.
  - This is the same pattern as `vendor/leaflet.markercluster.js`.
- **`.gitattributes`:** `public/vendor/** -text`. Otherwise `* text=auto
eol=lf` rewrites `leaflet.css`, which ships with CRLF, and the browser
refuses it for the integrity mismatch. I hit exactly that while
preparing this.
- **`scripts/instrument-frontend.sh`:** now copies `vendor/`
recursively. The flat `cp public/vendor/*` left the E2E server without
`vendor/leaflet-1.9.4/`, which showed up as `L is not defined`.
- **New test `tests/unit/test-vendored-libs.js`:**
  - no off-site `<script src>` or stylesheet in the served pages
  - every `integrity` attribute matches its vendored file
  - the images Leaflet's stylesheet references exist

  I checked that it fails against the old `index.html`.

## Checks

- `sh test-all.sh` passes.
- These E2E suites pass against the fixture: `test-e2e-playwright.js`,
`test-analytics-fluid-charts.js`, `test-issue-1236-map-mobile-e2e.js`
and `test-issue-1329-map-controls-accordion-e2e.js`.
- On a live instance, a headless browser with `unpkg.com`, `jsdelivr`
and `cdnjs` blocked:
  - the observer page renders all four charts
  - the map loads tiles and markers
  - `typeof L` and `typeof Chart` are defined
  - no CDN request is made and no page error occurs

## Not in this PR

Swagger UI on `/api/docs` (`cmd/server/openapi.go`) still loads
`swagger-ui-dist` from unpkg. I can vendor it in a follow-up if wanted.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 15:43:16 +01:00

55 lines
2.4 KiB
JavaScript

/**
* The pages load their third-party libraries (Leaflet, leaflet.heat,
* Chart.js) from public/vendor/, not from a CDN: no request to a third party
* on every page view, and maps and charts keep working where the CDN is
* blocked or down. This pins both halves:
* - no <script src> or <link href> in the served pages points off-site
* - every vendored file carrying an integrity attribute matches it, so a
* re-download or upgrade that changes the bytes fails here, not in a
* browser that refuses to run the script.
*
* Usage: node tests/unit/test-vendored-libs.js
*/
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const PUBLIC = path.join(__dirname, '..', '..', 'public');
const PAGES = ['index.html', 'area-map.html', 'geofilter-builder.html'];
let pass = 0, fail = 0;
function test(name, fn) {
try { fn(); pass++; console.log(' ✅ ' + name); } catch (e) { fail++; console.log(' ❌ ' + name + ': ' + e.message); }
}
for (const page of PAGES) {
const html = fs.readFileSync(path.join(PUBLIC, page), 'utf8');
const tags = html.match(/<(script|link)\b[^>]*>/g) || [];
test(page + ': no script or stylesheet from another origin', () => {
const offsite = tags.filter((t) => /\b(src|href)="(https?:)?\/\//.test(t) && (/<script/.test(t) || /rel="stylesheet"/.test(t)));
if (offsite.length) throw new Error(offsite.join(' | '));
});
for (const tag of tags) {
const ref = (tag.match(/\b(?:src|href)="([^"?]+)/) || [])[1];
const sri = (tag.match(/\bintegrity="([^"]+)"/) || [])[1];
if (!ref || !sri) continue;
test(page + ': ' + ref + ' matches its integrity hash', () => {
const [alg, want] = sri.split(/-(.+)/);
const got = crypto.createHash(alg).update(fs.readFileSync(path.join(PUBLIC, ref))).digest('base64');
if (got !== want) throw new Error(alg + ' is ' + got + ', integrity says ' + want);
});
}
}
test('Leaflet images referenced by its stylesheet are vendored', () => {
const dir = path.join(PUBLIC, 'vendor', 'leaflet-1.9.4');
const css = fs.readFileSync(path.join(dir, 'leaflet.css'), 'utf8');
const missing = [...css.matchAll(/url\((images\/[^)]+)\)/g)].map((m) => m[1])
.filter((f) => !fs.existsSync(path.join(dir, f)));
if (missing.length) throw new Error('missing ' + missing.join(', '));
});
console.log(`\nVendored libraries: ${pass} passed, ${fail} failed`);
process.exit(fail > 0 ? 1 : 0);