PageSourceSearch

https://nailartai.app/sw.js

js nailartai.app collected 2026-10-06 06:46:31 UTC 5,770 bytes, 203 lines download raw bytes

1// Service Worker for AI Nail Art Studio
2// Provides offline support and caching for better performance
3
4const CACHE_NAME = 'nail-art-studio-v4';
5const STATIC_CACHE = 'nail-art-static-v4';
6const API_CACHE = 'nail-art-api-v4';
7const IMAGE_CACHE = 'nail-art-images-v4';
8
9// Assets to cache immediately
10const STATIC_ASSETS = [
11  '/',
12  '/manifest.json',
13  '/favicon.ico',
14  '/apple-touch-icon.png',
15];
16
17// API endpoints to cache
18const API_ENDPOINTS = [
19  '/api/gallery',
20  '/api/categories',
21];
22
23// Cacheable image origins
24const CACHEABLE_ORIGINS = [
25  'cdn.nailartai.app',
26  'pub-05b5ee1a83754aa6b4fcd974016ecde8.r2.dev',
27  'pub-f94b6dc4538f33bcd1553dcdda15b36d.r2.dev',
28  'pub-fc15073de2e24f7bacc00c238f8ada7d.r2.dev'
29];
30
31// Install event - cache static assets
32self.addEventListener('install', (event) => {
33  console.log('Service Worker: Installing...');
34
35  event.waitUntil(
36    caches.open(STATIC_CACHE).then((cache) => {
37      console.log('Service Worker: Caching static assets');
38      return cache.addAll(STATIC_ASSETS);
39    })
40  );
41
42  // Skip waiting to activate immediately
43  self.skipWaiting();
44});
45
46// Activate event - clean up old caches
47self.addEventListener('activate', (event) => {
48  console.log('Service Worker: Activating...');
49
50  event.waitUntil(
51    caches.keys().then((cacheNames) => {
52      return Promise.all(
53        cacheNames.map((cacheName) => {
54          if (cacheName !== STATIC_CACHE && cacheName !== API_CACHE && cacheName !== IMAGE_CACHE) {
55            console.log('Service Worker: Deleting old cache:', cacheName);
56            return caches.delete(cacheName);
57          }
58        })
59      );
60    })
61  );
62
63  // Take control of all clients immediately
64  self.clients.claim();
65});
66
67// Fetch event - handle requests with caching strategies
68self.addEventListener('fetch', (event) => {
69  const { request } = event;
70  const url = new URL(request.url);
71
72  // Handle API requests
73  if (url.pathname.startsWith('/api/')) {
74    event.respondWith(handleApiRequest(request));
75  }
76  // Handle image requests from R2/CDN
77  else if (CACHEABLE_ORIGINS.some(origin => url.hostname.includes(origin))) {
78    event.respondWith(handleImageRequest(request));
79  }
80  // Handle static assets and pages
81  else {
82    event.respondWith(handleStaticRequest(request));
83  }
84});
85
86// API request strategy: Network first, cache fallback
87async function handleApiRequest(request) {
88  try {
89    // Try network first
90    const networkResponse = await fetch(request);
91
92    // If successful, cache the response (only GET requests)
93    if (networkResponse.ok && request.method === 'GET') {
94      const cache = await caches.open(API_CACHE);
95      cache.put(request, networkResponse.clone());
96    }
97
98    return networkResponse;
99  } catch (error) {
100    // Network failed, try cache (only for GET requests)
101    if (request.method === 'GET') {
102      const cachedResponse = await caches.match(request);
103      if (cachedResponse) {
104        return cachedResponse;
105      }
106    }
107
108    // No cache available, return error
109    return new Response(JSON.stringify({ error: 'Offline' }), {
110      status: 503,
111      headers: { 'Content-Type': 'application/json' }
112    });
113  }
114}
115
116// Static request strategy: Cache first, network fallback
117async function handleStaticRequest(request) {
118  // App routes must always prefer the network. Caching an HTML/RSC response
119  // here can preserve an old React bundle after a deployment, which means a
120  // signed-in user can see UI from one release with behavior from another.
121  if (request.mode === 'navigate') {
122    try {
123      return await fetch(request);
124    } catch (error) {
125      const fallback = await caches.match(request);
126      if (fallback) return fallback;
127      const homeFallback = await caches.match('/');
128      if (homeFallback) return homeFallback;
129      return new Response('Offline', { status: 503 });
130    }
131  }
132
133  // Try cache first for same-origin requests
134  if (request.url.startsWith(self.location.origin)) {
135    const cachedResponse = await caches.match(request);
136    if (cachedResponse) {
137      return cachedResponse;
138    }
139  }
140
141  try {
142    // Network request
143    const networkResponse = await fetch(request);
144
145    // Cache successful responses (only GET requests)
146    if (networkResponse.ok &&
147        request.url.startsWith(self.location.origin) &&
148        request.method === 'GET' &&
149        request.destination !== 'document') {
150      const cache = await caches.open(STATIC_CACHE);
151      cache.put(request, networkResponse.clone());
152    }
153
154    return networkResponse;
155  } catch (error) {
156    // If it's a navigation request and we have the main page cached, return it
157    if (request.mode === 'navigate') {
158      const fallback = await caches.match('/');
159      if (fallback) {
160        return fallback;
161      }
162    }
163
164    return new Response('Offline', { status: 503 });
165  }
166}
167
168// Background sync for form submissions when offline
169self.addEventListener('sync', (event) => {
170  if (event.tag === 'background-sync') {
171    event.waitUntil(syncOfflineData());
172  }
173});
174
175// Image request strategy: Cache first with 7-day TTL
176async function handleImageRequest(request) {
177  // Only cache GET requests
178  if (request.method !== 'GET') {
179    return fetch(request);
180  }
181
182  const cache = await caches.open(IMAGE_CACHE);
183  const cachedResponse = await caches.match(request);
184  
185  if (cachedResponse) {
186    return cachedResponse;
187  }
188  
189  try {
190    const networkResponse = await fetch(request);
191    if (networkResponse.ok) {
192      cache.put(request, networkResponse.clone());
193    }
194    return networkResponse;
195  } catch (error) {
196    return new Response('Image unavailable', { status: 503 });
197  }
198}
199
200async function syncOfflineData() {
201  // Handle any offline form submissions or data sync
202  console.log('Service Worker: Background sync triggered');
203}

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.