PageSourceSearch

https://boundary-graph.netlify.app/sw.js

js boundary-graph.netlify.app collected 2026-10-03 10:32:10 UTC 6,607 bytes, 242 lines download raw bytes

1// Service Worker for Boundary Graph
2// Enhanced caching for mobile performance
3
4const CACHE_NAME = 'boundary-graph-v1';
5const API_CACHE_NAME = 'boundary-graph-api-v1';
6
7// Assets to cache immediately
8const STATIC_ASSETS = [
9  '/',
10  '/bg-logo.png',
11  '/icon.png',
12  '/icon-512.png',
13  '/manifest.json'
14];
15
16// API endpoints to cache with different strategies
17const API_ENDPOINTS = [
18  '/api/overview',
19  '/api/batsmen/top',
20  '/api/bowlers/top',
21  '/api/teams',
22  '/api/venues'
23];
24
25// Install event - cache static assets
26self.addEventListener('install', event => {
27  event.waitUntil(
28    caches.open(CACHE_NAME)
29      .then(cache => {
30        console.log('📦 Caching static assets...');
31        return cache.addAll(STATIC_ASSETS);
32      })
33      .then(() => {
34        console.log('✅ Static assets cached successfully');
35        return self.skipWaiting();
36      })
37      .catch(err => {
38        console.error('❌ Cache installation failed:', err);
39      })
40  );
41});
42
43// Activate event - clean up old caches
44self.addEventListener('activate', event => {
45  event.waitUntil(
46    caches.keys()
47      .then(cacheNames => {
48        return Promise.all(
49          cacheNames.map(cacheName => {
50            if (cacheName !== CACHE_NAME && cacheName !== API_CACHE_NAME) {
51              console.log('🗑️ Deleting old cache:', cacheName);
52              return caches.delete(cacheName);
53            }
54          })
55        );
56      })
57      .then(() => {
58        console.log('✅ Cache cleanup completed');
59        return self.clients.claim();
60      })
61  );
62});
63
64// Fetch event - implement caching strategies
65self.addEventListener('fetch', event => {
66  const { request } = event;
67  const url = new URL(request.url);
68
69  // Handle API requests with network-first strategy
70  if (url.pathname.startsWith('/api/')) {
71    event.respondWith(
72      networkFirstStrategy(request, API_CACHE_NAME)
73    );
74    return;
75  }
76
77  // Handle static assets with cache-first strategy
78  if (request.method === 'GET' && 
79      (request.destination === 'document' || 
80       request.destination === 'script' || 
81       request.destination === 'style' ||
82       request.destination === 'image')) {
83    event.respondWith(
84      cacheFirstStrategy(request, CACHE_NAME)
85    );
86    return;
87  }
88});
89
90// Network-first strategy for API calls
91async function networkFirstStrategy(request, cacheName) {
92  try {
93    // Try network first
94    const networkResponse = await fetch(request);
95    
96    if (networkResponse.ok) {
97      // Cache successful response for mobile users
98      const cache = await caches.open(cacheName);
99      cache.put(request, networkResponse.clone());
100      
101      console.log('📡 Network response cached:', request.url);
102      return networkResponse;
103    }
104    
105    throw new Error('Network response not ok');
106  } catch (error) {
107    // Fall back to cache
108    console.log('🔄 Network failed, trying cache for:', request.url);
109    const cachedResponse = await caches.match(request);
110    
111    if (cachedResponse) {
112      console.log('📋 Serving from cache:', request.url);
113      return cachedResponse;
114    }
115    
116    // Return offline fallback for API calls
117    return new Response(
118      JSON.stringify({ 
119        error: 'Offline', 
120        message: 'Data temporarily unavailable. Please check your connection.' 
121      }),
122      {
123        status: 503,
124        statusText: 'Service Unavailable',
125        headers: { 'Content-Type': 'application/json' }
126      }
127    );
128  }
129}
130
131// Cache-first strategy for static assets
132async function cacheFirstStrategy(request, cacheName) {
133  try {
134    // Try cache first
135    const cachedResponse = await caches.match(request);
136    
137    if (cachedResponse) {
138      console.log('⚡ Serving from cache:', request.url);
139      return cachedResponse;
140    }
141    
142    // Fall back to network
143    console.log('📡 Cache miss, fetching from network:', request.url);
144    const networkResponse = await fetch(request);
145    
146    if (networkResponse.ok) {
147      // Cache for future use
148      const cache = await caches.open(cacheName);
149      cache.put(request, networkResponse.clone());
150      console.log('📦 Cached new resource:', request.url);
151    }
152    
153    return networkResponse;
154  } catch (error) {
155    console.error('❌ Both cache and network failed for:', request.url);
156    
157    // Return a basic offline page for navigation requests
158    if (request.mode === 'navigate') {
159      return new Response(
160        `<!DOCTYPE html>
161        <html>
162          <head>
163            <title>Boundary Graph - Offline</title>
164            <meta name="viewport" content="width=device-width, initial-scale=1">
165            <style>
166              body { font-family: system-ui, sans-serif; text-align: center; padding: 2rem; }
167              .offline { color: #64748b; }
168            </style>
169          </head>
170          <body>
171            <h1>🏏 Boundary Graph</h1>
172            <p class="offline">You're offline. Please check your connection.</p>
173          </body>
174        </html>`,
175        { 
176          headers: { 'Content-Type': 'text/html' } 
177        }
178      );
179    }
180    
181    throw error;
182  }
183}
184
185// Background sync for mobile data efficiency
186self.addEventListener('sync', event => {
187  if (event.tag === 'background-sync-api') {
188    event.waitUntil(
189      syncApiData()
190    );
191  }
192});
193
194// Sync essential API data in background
195async function syncApiData() {
196  try {
197    console.log('🔄 Background syncing API data...');
198    
199    const cache = await caches.open(API_CACHE_NAME);
200    const baseUrl = 'https://boundary-graph.onrender.com';
201    
202    // Sync essential endpoints
203    const endpoints = [
204      '/api/overview',
205      '/api/batsmen/top?limit=20',
206      '/api/bowlers/top?limit=20'
207    ];
208    
209    for (const endpoint of endpoints) {
210      try {
211        const response = await fetch(baseUrl + endpoint);
212        if (response.ok) {
213          await cache.put(endpoint, response);
214          console.log('✅ Synced:', endpoint);
215        }
216      } catch (err) {
217        console.log('⚠️ Sync failed for:', endpoint, err.message);
218      }
219    }
220    
221    console.log('✅ Background sync completed');
222  } catch (error) {
223    console.error('❌ Background sync failed:', error);
224  }
225}
226
227// Message handling for cache management
228self.addEventListener('message', event => {
229  if (event.data && event.data.type === 'SKIP_WAITING') {
230    self.skipWaiting();
231  }
232  
233  if (event.data && event.data.type === 'CLEAR_CACHE') {
234    event.waitUntil(
235      caches.keys().then(cacheNames => {
236        return Promise.all(
237          cacheNames.map(cacheName => caches.delete(cacheName))
238        );
239      })
240    );
241  }
242});

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.