openvibers/openvibe.shared sw.js
sw.js
| 1 | ; |
| 2 | /** |
| 3 | * openvibe-shared/sw — the server's side of the shared worker (openvibe-sw.js). |
| 4 | * |
| 5 | * A site that wants the shell's cache says so in its navbar (OpenVibeNavbar.init({ sw: true })) and serves the worker at |
| 6 | * /openvibe-sw.js, from this package, at scope "/". This module is the other half: what the worker needs to know, and the |
| 7 | * early hints that get the shell downloading before the HTML is even parsed. |
| 8 | * |
| 9 | * const sw = require('openvibe-shared/sw'); |
| 10 | * app.get('/openvibe-sw.js', (req, res) => res.sendFile(sw.workerPath())); // the worker itself |
| 11 | * app.get('/ov-sw-manifest.json', (req, res) => res.json(sw.manifest())); // the shell, by content hash |
| 12 | * app.use(sw.hints({ links: (req) => [ { href: assets.url('/shared/navbar.js'), as: 'script' } ] })); |
| 13 | * |
| 14 | * The early hints (RFC 8297): the browser gets the shell's URLs in a 103 response, before the HTML arrives, and starts |
| 15 | * fetching them while the server is still writing the page. The same links ride along in the Link header of the page itself, |
| 16 | * so a proxy that drops the 103 still hands the browser the hint. |
| 17 | * |
| 18 | * Nothing here touches the API, and nothing here decides what a page shows: the hints are about when a file starts loading, |
| 19 | * never about what it says. |
| 20 | */ |
| 21 | const path = require('path'); |
| 22 | |
| 23 | /** The release this helper ships in; the worker keeps the same number, and a test checks they agree. */ |
| 24 | const VERSION = '4.164.0'; |
| 25 | |
| 26 | /** The shared files every OpenVibe page loads: the shell. The worker keeps them in its cache. */ |
| 27 | const SHELL = Object.freeze([ |
| 28 | '/shared/navbar.js', |
| 29 | '/shared/theme-loader.js', |
| 30 | '/shared/web-runtime.js', |
| 31 | '/shared/footer.js', |
| 32 | '/shared/nav-icons.js', |
| 33 | '/shared/kit.css', |
| 34 | '/shared/kit-layout.css', |
| 35 | '/shared/ov-brand.woff2', |
| 36 | '/shared/ov-glyphs.woff2', |
| 37 | '/shared/ov-glyphs.svg', |
| 38 | ]); |
| 39 | |
| 40 | /** Where a worker asks whether it may keep working. A site may point it elsewhere for a test. */ |
| 41 | const KILL_SWITCH = 'https://openvibe.network/api/v1/sw-config'; |
| 42 | |
| 43 | /** The path every site serves the worker at. Same origin, scope "/". */ |
| 44 | const WORKER_PATH = '/openvibe-sw.js'; |
| 45 | |
| 46 | /** The file on disk: the worker this package ships. */ |
| 47 | function workerPath() { |
| 48 | return path.join(__dirname, 'openvibe-sw.js'); |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * The shell as JSON, for the site's /ov-sw-manifest.json. A site that serves the shell by content hash answers with its own |
| 53 | * hashed URLs; the plain list is the fallback. |
| 54 | */ |
| 55 | function manifest(shell) { |
| 56 | const given = (Array.isArray(shell) ? shell : []).filter((u) => typeof u === 'string' && u.startsWith('/')); |
| 57 | return { version: VERSION, shell: given.length ? given : SHELL }; |
| 58 | } |
| 59 | |
| 60 | /** One preload link, as the Link header and the 103 response write it. */ |
| 61 | function linkHeader(link) { |
| 62 | if (!link || !link.href) return ''; |
| 63 | let out = '<' + link.href + '>; rel=preload'; |
| 64 | if (link.as) out += '; as=' + link.as; |
| 65 | if (link.type) out += '; type=' + link.type; |
| 66 | if (link.crossOrigin) out += '; crossorigin'; |
| 67 | return out; |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * Send the early hints for a response. |
| 72 | * |
| 73 | * sw.earlyHints(res, [ { href: '/shared/navbar.js?v=abc123', as: 'script' } ]); |
| 74 | * |
| 75 | * The Link header rides on the page. The 103 itself goes out only where the proxy in front passes it on: `{ send103: true }` |
| 76 | * or OV_EARLY_HINTS=1 (nginx 1.29+ with `early_hints`). nginx 1.28 (the host's, 11-09) takes an upstream 103 as the final |
| 77 | * response and sends the real 200 as its body, so the default is the header only; Cloudflare's Early Hints read it. |
| 78 | * Returns true when a 103 was sent. |
| 79 | */ |
| 80 | function earlyHints(res, links, { send103 = process.env.OV_EARLY_HINTS === '1' } = {}) { |
| 81 | const list = (Array.isArray(links) ? links : []).filter((l) => l && l.href); |
| 82 | if (!list.length || !res) return false; |
| 83 | let sent = false; |
| 84 | if (send103 && typeof res.writeEarlyHints === 'function') { |
| 85 | try { |
| 86 | res.writeEarlyHints({ link: list.map(linkHeader).filter(Boolean) }); |
| 87 | sent = true; |
| 88 | } catch { /* the response was already on its way */ } |
| 89 | } |
| 90 | if (!res.headersSent) { |
| 91 | try { res.setHeader('Link', list.map(linkHeader).filter(Boolean).join(', ')); } catch { /* the header was set already */ } |
| 92 | } |
| 93 | return sent; |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Express middleware: send the early hints for a document request. |
| 98 | * |
| 99 | * app.use(sw.hints({ links: (req) => [ { href: navbarUrl, as: 'script' } ] })); |
| 100 | * |
| 101 | * Only GET and HEAD are hinted, and only when the request asks for a document: a file request already knows its own URL. |
| 102 | */ |
| 103 | function hints({ links, filter } = {}) { |
| 104 | const pick = typeof links === 'function' ? links : () => (Array.isArray(links) ? links : []); |
| 105 | return function openvibeEarlyHints(req, res, next) { |
| 106 | if (req.method !== 'GET' && req.method !== 'HEAD') return next(); |
| 107 | if (filter && filter(req, res) === false) return next(); |
| 108 | try { earlyHints(res, pick(req, res)); } catch { /* a hint is never worth a page */ } |
| 109 | return next(); |
| 110 | }; |
| 111 | } |
| 112 | |
| 113 | module.exports = { VERSION, SHELL, KILL_SWITCH, WORKER_PATH, workerPath, manifest, earlyHints, hints, linkHeader }; |
| 114 |