PageSourceSearch

https://joyjohnston.com/sw.js

js joyjohnston.com collected 2026-10-07 14:04:36 UTC 4,474 bytes, 152 lines download raw bytes

1const CACHE_NAME = 'joy-johnston-v1';
2const STATIC_CACHE_NAME = 'static-v1';
3const DYNAMIC_CACHE_NAME = 'dynamic-v1';
4
5// Assets to cache immediately
6const STATIC_ASSETS = [
7  '/',
8  '/src/main.tsx',
9  '/src/index.css',
10  '/favicon.ico',
11  'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'
12];
13
14// Assets to cache on demand
15const CACHEABLE_PATTERNS = [
16  /^https:\/\/fonts\.googleapis\.com/,
17  /^https:\/\/fonts\.gstatic\.com/,
18  /\/lovable-uploads\//,
19  /\.(js|css|png|jpg|jpeg|webp|svg|ico)$/
20];
21
22// Install event - cache static assets
23self.addEventListener('install', event => {
24  console.log('[SW] Installing...');
25  event.waitUntil(
26    caches.open(STATIC_CACHE_NAME)
27      .then(cache => {
28        console.log('[SW] Pre-caching static assets');
29        return cache.addAll(STATIC_ASSETS);
30      })
31      .then(() => self.skipWaiting())
32  );
33});
34
35// Activate event - clean up old caches
36self.addEventListener('activate', event => {
37  console.log('[SW] Activating...');
38  event.waitUntil(
39    caches.keys().then(cacheNames => {
40      return Promise.all(
41        cacheNames
42          .filter(cacheName => 
43            cacheName !== STATIC_CACHE_NAME && 
44            cacheName !== DYNAMIC_CACHE_NAME
45          )
46          .map(cacheName => {
47            console.log('[SW] Deleting old cache:', cacheName);
48            return caches.delete(cacheName);
49          })
50      );
51    }).then(() => self.clients.claim())
52  );
53});
54
55// Fetch event - serve from cache with network fallback
56self.addEventListener('fetch', event => {
57  const { request } = event;
58  const url = new URL(request.url);
59
60  // Skip non-GET requests
61  if (request.method !== 'GET') return;
62
63  // Skip chrome-extension and other non-http requests
64  if (!url.protocol.startsWith('http')) return;
65
66  // Network first for API calls
67  if (url.pathname.includes('/api/') || url.hostname.includes('supabase')) {
68    event.respondWith(
69      fetch(request)
70        .catch(() => new Response('Offline', { status: 503 }))
71    );
72    return;
73  }
74
75  // Cache first for static assets
76  if (CACHEABLE_PATTERNS.some(pattern => pattern.test(request.url))) {
77    event.respondWith(
78      caches.match(request)
79        .then(cachedResponse => {
80          if (cachedResponse) {
81            // Update cache in background
82            fetch(request)
83              .then(response => {
84                if (response.ok) {
85                  caches.open(DYNAMIC_CACHE_NAME)
86                    .then(cache => cache.put(request, response.clone()));
87                }
88              })
89              .catch(() => {});
90            
91            return cachedResponse;
92          }
93
94          // Not in cache, fetch and cache
95          return fetch(request)
96            .then(response => {
97              if (response.ok) {
98                const responseClone = response.clone();
99                caches.open(DYNAMIC_CACHE_NAME)
100                  .then(cache => cache.put(request, responseClone));
101              }
102              return response;
103            })
104            .catch(() => {
105              // Return offline fallback for images
106              if (request.destination === 'image') {
107                return new Response(
108                  '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><rect width="200" height="200" fill="#f3f4f6"/><text x="100" y="100" text-anchor="middle" dy="0.3em" font-family="sans-serif" font-size="14" fill="#6b7280">Image unavailable</text></svg>',
109                  { headers: { 'Content-Type': 'image/svg+xml' } }
110                );
111              }
112              throw error;
113            });
114        })
115    );
116    return;
117  }
118
119  // Network first for everything else
120  event.respondWith(
121    fetch(request)
122      .catch(() => 
123        caches.match(request)
124          .then(cachedResponse => 
125            cachedResponse || new Response('Offline', { status: 503 })
126          )
127      )
128  );
129});
130
131// Background sync for failed requests
132self.addEventListener('sync', event => {
133  if (event.tag === 'background-sync') {
134    console.log('[SW] Background sync triggered');
135    // Handle any queued requests here
136  }
137});
138
139// Push notifications (if needed in future)
140self.addEventListener('push', event => {
141  if (event.data) {
142    const options = {
143      body: event.data.text(),
144      icon: '/favicon.ico',
145      badge: '/favicon.ico'
146    };
147    
148    event.waitUntil(
149      self.registration.showNotification('Joy Johnston', options)
150    );
151  }
152});

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.