PageSourceSearch

https://ecpr.eu/Scripts/serviceworker.js

js ecpr.eu collected 2026-09-24 08:36:32 UTC 11,878 bytes, 311 lines download raw bytes

1// Workbox 7.4.1, self-hosted: Google's workbox-cdn was discontinued at the 6.x line, and self-hosting
2// keeps worker installs/updates independent of any third-party CDN.
3importScripts('/Scripts/workbox/workbox-sw.js');
4workbox.setConfig({ modulePathPrefix: '/Scripts/workbox/' });
5
6self.addEventListener('message', (event) => {
7    if (event.data && event.data.type === 'SKIP_WAITING') {
8        self.skipWaiting();
9    }
10});
11
12self.addEventListener('message', (event) => {
13    console.log('[SW] Received message:', event.data);
14
15    if (event.data && event.data.type === 'CLEAR_PROGRAMME_CACHE') {
16        const cacheNamesToDelete = ['programme', 'programme-partials'];
17
18        caches.keys().then((names) => {
19            console.log('[SW] Existing caches:', names);
20        });
21
22        Promise.all(
23            cacheNamesToDelete.map(name => caches.delete(name))
24        ).then(results => {
25            console.log('[SW] Deleted caches:', cacheNamesToDelete.filter((_, i) => results[i]));
26        }).catch(err => {
27            console.error('[SW] Error clearing caches:', err);
28        });
29    }
30});
31
32
33self.__precacheManifest = [
34    {
35        "url": "/pwaOffline.html"
36    },
37    {
38        "url": "/Scripts/CoreJSBundle"
39    },
40    {
41        "url": "/Styles/CustomBootstrapCSSBundle"
42    },
43    {
44        "url": "/Styles/CoreCSSBundle"
45    },
46    {
47        "url": "/Styles/AdditionalCSSBundle"
48    },
49    {
50        "url": "/Styles/DevExpress"
51    },
52    {
53        "url": "/Content/DevExtremeBundle"
54    },
55    {
56        "url": "/Scripts/DevExtremeBundle"
57    },
58    {
59        "url": "/Script/AcademicProgrammeBundle"
60    },
61    {
62        "url": "/favicon.ico"
63    }
64].concat(self.__precacheManifest || []);
65workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
66
67workbox.routing.registerRoute(
68    // Cache Image File
69    /.*\.(?:png|jpg|jpeg|svg|gif|webp)/,
70    new workbox.strategies.CacheFirst({
71        cacheName: 'images',
72        plugins: [
73            new workbox.expiration.ExpirationPlugin({
74                maxEntries: 60,
75                maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
76            })
77        ]
78    })
79);
80
81workbox.routing.registerRoute(
82    new RegExp('https://platform\\.twitter\\.com.*'),
83    new workbox.strategies.NetworkOnly()
84);
85
86workbox.routing.registerRoute(
87    new RegExp('https://syndication\\.twitter\\.com.*'),
88    new workbox.strategies.NetworkOnly()
89);
90
91workbox.routing.registerRoute(
92    new RegExp('https://www\\.google-analytics\\.com.*'),
93    new workbox.strategies.NetworkOnly()
94);
95
96workbox.routing.registerRoute(
97    new RegExp('http://www\\.google-analytics\\.com.*'),
98    new workbox.strategies.NetworkOnly()
99);
100
101// The only cross-origin assets worth an offline copy (icon kit / consent CSS the shell needs).
102// Tightly bounded: cached no-cors responses are opaque and Chrome pads each one by several MB of
103// reported quota. All other cross-origin requests are passthrough - see setDefaultHandler.
104workbox.routing.registerRoute(
105    ({url}) => url.hostname === 'kit.fontawesome.com'
106            || url.hostname === 'ka-f.fontawesome.com'
107            || url.hostname === 'cdn.jsdelivr.net',
108    new workbox.strategies.StaleWhileRevalidate({
109        cacheName: 'ext-static',
110        plugins: [
111            new workbox.expiration.ExpirationPlugin({
112                maxEntries: 40,
113                maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
114                purgeOnQuotaError: true
115            })
116        ]
117    })
118);
119
120// SignalR URLs carry a unique token/timestamp per request (a cached copy can never be replayed) and
121// Admin pages are per-user and useless offline. The static /signalr/hubs proxy script is the
122// exception: it falls through to the bounded default so EP pages can still load it offline.
123workbox.routing.registerRoute(
124    ({url}) => {
125        const path = url.pathname.toLowerCase();
126        return (path.startsWith('/signalr') && path !== '/signalr/hubs') || path.startsWith('/admin');
127    },
128    new workbox.strategies.NetworkOnly()
129);
130
131workbox.routing.registerRoute(
132    new RegExp('\/MyEcpr/Account/Create*'),
133    new workbox.strategies.NetworkOnly()
134);
135
136workbox.routing.registerRoute(
137    // Case-insensitive: the live route is /Login (capital L), and a lowercase-only match would let the
138    // login page fall through to the default NetworkFirst handler and be cached.
139    new RegExp('\/login.*', 'i'),
140    new workbox.strategies.NetworkOnly()
141);
142
143workbox.routing.registerRoute(
144    new RegExp('\/Events\/AcademicProgramme\/Schedule.*'),
145    new workbox.strategies.StaleWhileRevalidate({
146        cacheName: "schedule",
147        plugins: [
148            // DevExtreme grids append a unique query string per sort/filter/page, so without an
149            // expiry every variant lives forever.
150            new workbox.expiration.ExpirationPlugin({
151                maxEntries: 100,
152                maxAgeSeconds: 300, // 5 Minutes
153                purgeOnQuotaError: true
154            })
155        ]
156    })
157);
158
159workbox.routing.registerRoute(
160    new RegExp('\/Events\/AcademicProgramme\/ScheduleGridView.*'),
161    new workbox.strategies.StaleWhileRevalidate({
162        cacheName: "schedule",
163        plugins: [
164            new workbox.expiration.ExpirationPlugin({
165                maxEntries: 100,
166                maxAgeSeconds: 300, // 5 Minutes
167                purgeOnQuotaError: true
168            })
169        ]
170    })
171);
172
173workbox.routing.registerRoute(
174    ({url}) => url.pathname.startsWith('/Events/AcademicProgramme/Programme'),
175    new workbox.strategies.StaleWhileRevalidate({
176        cacheName: 'programme',
177        plugins: [
178            new workbox.expiration.ExpirationPlugin({
179                maxAgeSeconds: 300, // 5 Minutes
180                purgeOnQuotaError: true
181            })
182        ]
183    })
184);
185
186workbox.routing.registerRoute(
187    ({url}) => url.pathname.includes('SectionGridView')
188        || url.pathname.includes('PanelGridView')
189        || url.pathname.includes('WorkshopGridView')
190        || url.pathname.includes('PaperGridView')
191        || url.pathname.includes('PeopleGridView'),
192    new workbox.strategies.StaleWhileRevalidate({
193        cacheName: 'programme-partials',
194        plugins: [
195            new workbox.expiration.ExpirationPlugin({
196                maxAgeSeconds: 300, // 5 Minutes
197                purgeOnQuotaError: true
198            })
199        ]
200    })
201);
202
203workbox.routing.registerRoute(
204    new RegExp('\/Events\/AcademicProgramme\/KeywordGridView.*'),
205    new workbox.strategies.StaleWhileRevalidate({
206        cacheName: "programme",
207        plugins: [
208            // Must match the Programme route above: entries written by a plugin-less strategy get no
209            // expiration timestamp, so the other route's plugin can never prune them.
210            new workbox.expiration.ExpirationPlugin({
211                maxAgeSeconds: 300, // 5 Minutes
212                purgeOnQuotaError: true
213            })
214        ]
215    })
216);
217
218// Meeting pages must never be served from cache - correctness, not performance. Remove when the
219// legacy VirtualEvent area is deleted.
220workbox.routing.registerRoute(
221    new RegExp('\/VirtualEvent\/VirtualMeeting.*'),
222    new workbox.strategies.NetworkOnly()
223);
224
225// Bounded LRU for same-origin GETs not matched above. The entry cap is load-bearing: '_='-busted and
226// grid-query URLs are unique per request, so an uncapped cache here grows without limit.
227const siteFallback = new workbox.strategies.NetworkFirst({
228    cacheName: 'site-fallback',
229    plugins: [
230        new workbox.expiration.ExpirationPlugin({
231            maxEntries: 150,
232            maxAgeSeconds: 7 * 24 * 60 * 60, // 7 Days
233            purgeOnQuotaError: true
234        })
235    ]
236});
237
238workbox.routing.setDefaultHandler((args) => {
239    const request = args.event.request;
240    if (request.method !== 'GET') return fetch(request);
241    // Cross-origin is passthrough: no-cors responses are opaque (can't be val
241idated) and Chrome pads
242    // each cached one by several MB of reported quota - caching them is all cost, no benefit.
243    if (new URL(request.url).origin !== self.location.origin) return fetch(request);
244    return siteFallback.handle(args);
245});
246
247workbox.routing.setCatchHandler(({ event }) => {
248    return caches.match('/pwaOffline.html');
249});
250
251// --- EventPlatform caching ---------------------------------------------------------------------
252// NetworkFirst throughout: online always shows the server's current version; the SW copy is only the
253// offline fallback. Per-user pages live in 'ep-personal', purged on sign-in/out (_Layout.cshtml,
254// Account/LoggedOut.cshtml) so accounts can't leak on a shared device; shared content persists.
255// Matchers are lowercase (MVC routes are case-insensitive); NetworkOnly rules must precede the catch-all.
256const epNetworkFirst = (cacheName) => new workbox.strategies.NetworkFirst({
257    cacheName,
258    networkTimeoutSeconds: 3, // fall back to the cached copy quickly on a flaky connection
259    plugins: [
260        new workbox.expiration.ExpirationPlugin({
261            maxEntries: 200, // bounds a heavy day of unique URLs (searches, ?focus= variants) per bucket
262            maxAgeSeconds: 24 * 60 * 60, // offline-copy lifespan (a full event day) - NOT the freshness window
263            purgeOnQuotaError: true
264        })
265    ]
266});
267
268// Must run live (NetworkOnly): sign-in/out (a cached auth response can't start or end a real server
269// session) and Zoom joins (the meeting page carries a time-sensitive SDK signature, so a cached copy is
270// expired and the join fails - the legacy VirtualMeeting rule above does the same).
271workbox.routing.registerRoute(
272    ({url}) => url.pathname.toLowerCase().startsWith('/eventplatform/account/'),
273    new workbox.strategies.NetworkOnly()
274);
275workbox.routing.registerRoute(
276    ({url}) => url.pathname.toLowerCase().startsWith('/eventplatform/meeting/'),
277    new workbox.strategies.NetworkOnly()
278);
279
280// Shared, user-agnostic content - cached for offline, never purged on auth.
281workbox.routing.registerRoute(
282    ({url}) => url.pathname.toLowerCase().startsWith('/eventplatform/programme/loadlistpartialview'),
283    epNetworkFirst('ep-programme-partials')
284);
285workbox.routing.registerRoute(
286    ({url}) => {
287        const path = url.pathname.toLowerCase();
288        return path.startsWith('/eventplatform/exhibitors')
289            || path.startsWith('/eventplatform/map')
290            || path.startsWith('/eventplatform/guide');
291    },
292    epNetworkFirst('ep-content')
293);
294
295// Everything else under /EventPlatform/ is per-user - cached for offline in the purgeable bucket.
296workbox.routing.registerRoute(
297    ({url}) => url.pathname.toLowerCase().startsWith('/eventplatform/'),
298    epNetworkFirst('ep-personal')
299);
300
301// On activate, delete buckets no strategy writes to (VirtualEvent-era names + the unnamed runtime
302// cache) and caches that can hold timestamp-less entries ExpirationPlugin can never prune
303// ('schedule'/'programme' - both repopulate fresh). Remove the VE names when that area is deleted.
304// workbox-sw lazy-loads modules via importScripts on first property access, which browsers disallow
305// inside event handlers after install - so resolve the runtime cache name at evaluation time.
306const workboxRuntimeCacheName = workbox.core.cacheNames.runtime;
307self.addEventListener('activate', (event) => {
308    const retiredCaches = ['splashScreen', 'exhibitorList', 'exhibitorsDetails', 'credits', 'helpDesk', 'map', 'highlights',
309        workboxRuntimeCacheName, 'schedule', 'programme'];
310    event.waitUntil(Promise.all(retiredCaches.map((name) => caches.delete(name))));
311});

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.