openvibers/openvibe.shared sw.js

sw.js

main 5.0 KB · 114 lines · javascript

1'use strict';
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 */
21const path = require('path');
22
23/** The release this helper ships in; the worker keeps the same number, and a test checks they agree. */
24const VERSION = '4.164.0';
25
26/** The shared files every OpenVibe page loads: the shell. The worker keeps them in its cache. */
27const 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. */
41const KILL_SWITCH = 'https://openvibe.network/api/v1/sw-config';
42
43/** The path every site serves the worker at. Same origin, scope "/". */
44const WORKER_PATH = '/openvibe-sw.js';
45
46/** The file on disk: the worker this package ships. */
47function 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 */
55function 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. */
61function 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 */
80function 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 */
103function 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
113module.exports = { VERSION, SHELL, KILL_SWITCH, WORKER_PATH, workerPath, manifest, earlyHints, hints, linkHeader };
114