PageSourceSearch

https://drophighlight.com/sw.js

js drophighlight.com collected 2026-09-30 06:39:24 UTC 4,701 bytes, 170 lines download raw bytes

1/**
2 * Service Worker for aggressive asset caching
3 * Only active in production environments
4 */
5
6const CACHE_VERSION = 'v3';
7const IMAGE_CACHE = `images-${CACHE_VERSION}`;
8const STATIC_CACHE = `static-${CACHE_VERSION}`;
9
10// Explicit production hostnames
11const PRODUCTION_HOSTS = [
12  'drophighlight.com',
13  'www.drophighlight.com',
14  'drop-highlight.lovable.app'
15];
16
17// Patterns that indicate non-production environments (take precedence)
18const NON_PRODUCTION_PATTERNS = [
19  'localhost',
20  '127.0.0.1',
21  '-preview--',      // Lovable preview
22  '.pages.dev'       // Cloudflare Pages
23];
24
25/**
26 * Check if hostname is a production environment
27 */
28function isProductionHost(hostname) {
29  // Non-production patterns take precedence
30  if (NON_PRODUCTION_PATTERNS.some(pattern => hostname.includes(pattern))) {
31    return false;
32  }
33  
34  // Check for exact production host or subdomain
35  return PRODUCTION_HOSTS.includes(hostname) || 
36         hostname.endsWith('.drophighlight.com');
37}
38
39/**
40 * Cache-first strategy with expiration
41 */
42async function cacheFirst(request, cacheName, maxAgeSeconds) {
43  const cache = await caches.open(cacheName);
44  const cached = await cache.match(request);
45  
46  if (cached) {
47    const dateHeader = cached.headers.get('sw-cached-at');
48    if (dateHeader) {
49      const age = Date.now() - parseInt(dateHeader, 10);
50      if (age < maxAgeSeconds * 1000) {
51        return cached;
52      }
53    }
54  }
55  
56  try {
57    const response = await fetch(request);
58    if (response.ok) {
59      const responseToCache = response.clone();
60      const headers = new Headers(responseToCache.headers);
61      headers.set('sw-cached-at', Date.now().toString());
62      
63      const modifiedResponse = new Response(responseToCache.body, {
64        status: responseToCache.status,
65        statusText: responseToCache.statusText,
66        headers
67      });
68      
69      cache.put(request, modifiedResponse);
70    }
71    return response;
72  } catch (error) {
73    // If fetch fails and we have cached version (even expired), use it
74    if (cached) return cached;
75    throw error;
76  }
77}
78
79/**
80 * Stale-while-revalidate strategy
81 */
82async function staleWhileRevalidate(request, cacheName, maxAgeSeconds) {
83  const cache = await caches.open(cacheName);
84  const cached = await cache.match(request);
85  
86  // Start fetch in background
87  const fetchPromise = fetch(request).then(response => {
88    if (response.ok) {
89      const responseToCache = response.clone();
90      const headers = new Headers(responseToCache.headers);
91      headers.set('sw-cached-at', Date.now().toString());
92      
93      const modifiedResponse = new Response(responseToCache.body, {
94        status: responseToCache.status,
95        statusText: responseToCache.statusText,
96        headers
97      });
98      
99      cache.put(request, modifiedResponse);
100    }
101    return response;
102  }).catch(() => cached);
103  
104  // Return cached immediately if available
105  if (cached) {
106    return cached;
107  }
108  
109  return fetchPromise;
110}
111
112// Install event - skip waiting to activate immediately
113self.addEventListener('install', () => {
114  self.skipWaiting();
115});
116
117// Activate event - claim clients immediately
118self.addEventListener('activate', (event) => {
119  event.waitUntil(
120    Promise.all([
121      clients.claim(),
122      // Clean old caches
123      caches.keys().then(keys => {
124        return Promise.all(
125          keys
126            .filter(key => !key.includes(CACHE_VERSION))
127            .map(key => caches.delete(key))
128        );
129      })
130    ])
131  );
132});
133
134// Fetch event - intercept requests
135self.addEventListener('fetch', (event) => {
136  // ⚠️ CRITICAL: Only cache in production
137  if (!isProductionHost(self.location.hostname)) {
138    return; // Bypass - let browser handle fetch normally
139  }
140  
141  const url = new URL(event.request.url);
142  
143  // Only handle GET requests
144  if (event.request.method !== 'GET') {
145    return;
146  }
147  
148  // Supabase Storage images - aggressive cache (7 days)
149  if (url.hostname.includes('supabase.co') && 
150      (url.pathname.includes('/thumbnails/') || 
151       url.pathname.includes('/covers/') ||
152       url.pathname.includes('/content/') ||
153       url.pathname.includes('/logos/'))) {
154    event.respondWith(cacheFirst(event.request, IMAGE_CACHE, 7 * 24 * 60 * 60));
155    return;
156  }
157  
158  // Local static images - cache for 1 day
159  if (url.pathname.match(/\.(png|jpg|jpeg|webp|ico|svg|gif)$/) && 
160      url.hostname === self.location.hostname) {
161    event.respondWith(cacheFirst(event.request, STATIC_CACHE, 24 * 60 * 60));
162    return;
163  }
164  
165  // Fonts - cache for 30 days
166  if (url.pathname.match(/\.(woff|woff2|ttf|otf)$/)) {
167    event.respondWith(cacheFirst(event.request, STATIC_CACHE, 30 * 24 * 60 * 60));
168    return;
169  }
170});

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.