openvibers/openvibe.shared cache-policy.js
cache-policy.js
| 1 | ; |
| 2 | /** |
| 3 | * openvibe-shared/cache-policy — the one cache rule every OpenVibe site uses, so no site has to |
| 4 | * remember it and no site invents its own numbers: a content-addressed asset is immutable for a |
| 5 | * year, everything else is a short public window with a short stale-while-revalidate, and a page |
| 6 | * that is personalised for the person reading it is never cached by anybody. |
| 7 | * |
| 8 | * const cache = require('openvibe-shared/cache-policy'); |
| 9 | * res.setHeader('Cache-Control', cache.assetHeaders(req.url)); // ?v=<hash> → immutable |
| 10 | * res.setHeader('Cache-Control', cache.htmlHeaders()); // public, max-age=120, swr 3600 |
| 11 | * app.use(cache.applyHtml({ private: req.path === '/account' })); |
| 12 | * |
| 13 | * A URL is content-addressed when it carries `?v=<hash>` (what serve.url() hands out) or a hashed |
| 14 | * filename (`app.6f2a91c3.js`, `styles-a1b2c3d4e5.css`). Immutable is only ever right for those: |
| 15 | * the bytes cannot change under the same URL. Everything else may change at any moment, so it gets |
| 16 | * five minutes plus five more of stale-while-revalidate — a visitor is served the old copy for a |
| 17 | * second while the new one is fetched, and nobody waits on the network twice. 4.227.0: it was a day, |
| 18 | * and the first visit to each site after a release ran the old navbar.js (sites load it without ?v=), |
| 19 | * so the owner's phone showed an old menu hours after the new one shipped (11-12). After ten quiet |
| 20 | * minutes a browser asks again: a 304 for an unchanged file. |
| 21 | */ |
| 22 | const IMMUTABLE = 'public, max-age=31536000, immutable'; |
| 23 | const ASSET_MAX_AGE = 300; |
| 24 | const ASSET_SWR = 300; |
| 25 | |
| 26 | /** True when `path` is content-addressed: `?v=<hash>` or a hash in the filename. */ |
| 27 | function isHashed(path) { |
| 28 | const s = String(path == null ? '' : path); |
| 29 | if (/(?:[?&]|&)v=[0-9a-f]{8,64}\b/.test(s)) return true; |
| 30 | const file = s.split(/[?#]/)[0]; |
| 31 | return /[.\-_][0-9a-f]{8,64}(?=[.\-_/]|$)/.test(file); |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * The Cache-Control for a static asset. `hashed` defaults to the URL's own content-address; pass |
| 36 | * it explicitly when the caller knows (a build manifest, a filename it generated itself). |
| 37 | */ |
| 38 | function assetHeaders(path, { hashed = isHashed(path), maxAge = ASSET_MAX_AGE, swr = ASSET_SWR } = {}) { |
| 39 | if (hashed) return IMMUTABLE; |
| 40 | return `public, max-age=${maxAge}, stale-while-revalidate=${swr}`; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * The Cache-Control for an HTML page. `private: true` (the default when a page is personalised) |
| 45 | * is `private, no-store`: no shared cache, no browser cache, nothing left on a shared machine. |
| 46 | */ |
| 47 | function htmlHeaders({ private: isPrivate = false, maxAge = 120, swr = 3600 } = {}) { |
| 48 | if (isPrivate) return 'private, no-store'; |
| 49 | return `public, max-age=${maxAge}, stale-while-revalidate=${swr}`; |
| 50 | } |
| 51 | |
| 52 | /** Express/connect middleware: sets the HTML policy on every GET/HEAD response that has no policy yet. */ |
| 53 | function applyHtml(opts = {}) { |
| 54 | const value = htmlHeaders(opts); |
| 55 | return function openvibeCachePolicy(req, res, next) { |
| 56 | if (req.method !== 'GET' && req.method !== 'HEAD') return next(); |
| 57 | if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', value); |
| 58 | return next(); |
| 59 | }; |
| 60 | } |
| 61 | |
| 62 | module.exports = { IMMUTABLE, isHashed, assetHeaders, htmlHeaders, applyHtml }; |
| 63 |