PageSourceSearch

https://www.medino.com/service-worker.js

js medino.com collected 2026-10-02 04:37:42 UTC 5,170 bytes, 148 lines download raw bytes

1'use strict';
2
3const TTL_MS = 60*1000;
4// The pages will live in RAM so keep it tight
5const MAX_ENTRIES = 5;
6// Like normal map, but:
7// 1. Entries have a TTL
8// 2. Can only hold MAX_ENTRIES elements.
9class FixedSizeTTLMap {
10	#map = new Map(); // key -> {value, cachedAt}
11
12	get(url) {
13		const entry = this.#map.get(url);
14		return (entry && !this.#expired(entry)) ? entry.value : null;
15	}
16
17	#expired(entry) {
18		return Date.now() - entry.cachedAt >= TTL_MS;
19	}
20
21
22	set(url, value) {
23		this.#map.set(url, {value, cachedAt: Date.now()});
24		this.#cleanup();
25	}
26
27	clear() {
28		this.#map.clear();
29	}
30
31	#cleanup() {
32		// Remove expired
33		for (const [url, entry] of this.#map) {
34			if (this.#expired(entry)) {
35				this.#map.delete(url);
36			}
37		}
38
39		if (this.#map.size <= MAX_ENTRIES) return; // Fast return if below capacity
40
41		// Evict oldest by cachedAt
42		const sorted = [...this.#map.entries()].sort((a, b) => a[1].cachedAt - b[1].cachedAt);
43		while (sorted.length > MAX_ENTRIES) this.#map.delete(sorted.shift()[0]);
44	}
45}
46
47
48// The offline page lives in it's own (permanent) cache storage.
49const OFFLINE_PAGE_CACHE_NAME = 'cache3';
50const OFFLINE_PAGE_URL = 'https://cdn.medino.com/html/offline_v3.html';
51const offlinePageCacheReady = caches.open(OFFLINE_PAGE_CACHE_NAME);
52
53// The preload caches doesn't need to persist across SW restarts,
54// so it can live in memory.
55const preloadCache = new FixedSizeTTLMap();
56
57// Used to abort in-progress preload requests
58let fetchController = new AbortController();
59
60
61self.addEventListener('install', event => {
62	// Add the offline page to the cache
63	offlinePageCacheReady.then(cache => cache.add(OFFLINE_PAGE_URL));
64	self.skipWaiting(); // Take over from any earlier SW ASAP.
65});
66
67self.addEventListener('activate', () => {
68	// Take over fetch handling right away, as opposed to after next page load
69	self.clients.claim().then(() => { console.log('Preloading SW now in control.'); });
70});
71
72self.addEventListener('message', event => {
73	// The only messages involved at the moment are preload request messages.
74	const url = event.data;
75
76	if (preloadCache.get(url)) return; // already cached
77
78    const referrer = event.source.url;
79
80	const fetchAndCache = async () => {
81		let response;
82		try {
83			response = await fetch(url, {credentials: 'same-origin', // include cookies
84			                             referrer: referrer,
85				                         // We can cancel the request
86				                         signal: fetchController.signal,
87				                         // You can't serve redirected responses in a fetch handler
88				                         redirect: 'manual'});
89		} catch (e) {
90			// AbortError is expected, log any other errors
91			if (e.name !== 'AbortError') console.error(e);
92			return null;
93		}
94		// Here we need to do some tricky stuff to allow the cached response to be re-used multiple times.
95		// As the response.body is a stream, it can otherwise be consumed only once.
96		//
97		// Opaque redirects happen when a redirect request is attempted in redirect:manual mode.
98		// They hide headers & status code from JS, but the browser can use them just fine.
99		// Opaque redirects have a null body so the response can be reused directly.
100		if (response.type === 'opaqueredirect') return () => response.clone();
101		// Eagerly consume the body to save ~10ms when serving the preloaded response
102		const body = await response.arrayBuffer();
103		// Each request needs to get a new Response, because the body can only be consumed once, as mentioned above.
104		// Thus we create a new response in a factory function from the body we captured before.
105		return () => new Response(body, {status: response.status, headers: response.headers});
106	};
107	preloadCache.set(url, fetchAndCache());
108});
109
110
111
112async function serveNavigation(request) {
113	const fetchAndCache = preloadCache.get(request.url);
114	const makeResponse = fetchAndCache ? await fetchAndCache : null;
115	// Return cached response if it exists
116	if (makeResponse) return makeResponse();
117
118	try {
119		// Otherwise falls back to a normal request
120		return await fetch(request);
121	} catch (error) {
122		// If that fails too, show the offline page
123		console.debug("Network request Failed. Serving offline page " + error);
124		const cache = await offlinePageCacheReady;
125		// The fallback is there in case the offline page hasn't loaded yet
126		return (await cache.match(OFFLINE_PAGE_URL)) || Response.error();
127	}
128}
129
130
131self.addEventListener('fetch', function(event) {
132	// Use built in fetch handler if not medino domain
133	if (!event.request.url.startsWith(self.location.origin)) return;
134
135	if (['POST','PUT','PATCH','DELETE'].includes(event.request.method)) {
136		fetchController.abort(); // Cancel in-flight preloads
137		fetchController = new AbortController(); // Replace the spent controller
138		preloadCache.clear();
139		return;
140	}
141
142	// only if it's a proper load of a new page or refresh
143	if (event.request.mode === 'navigate' &&
144		// the referrer = url thing is needed for the reload on the offline page, as this one does not get the .destination set
145		(event.request.destination === 'document' || event.request.referrer === event.request.url)) {
146		event.respondWith(serveNavigation(event.request));
147	}
148});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.