Skip to content

Commit 0d07c9f

Browse files
authored
Update sw.js
1 parent fdbf145 commit 0d07c9f

1 file changed

Lines changed: 186 additions & 69 deletions

File tree

public/sw.js

Lines changed: 186 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
// Token is stored securely in IndexedDB
44

55
const GITHUB_API_BASE = 'https://api.github.com';
6-
const GITHUB_API_CACHE = 'github-api-cache-v2';
7-
const STATIC_CACHE = 'static-cache-v2';
8-
const EXTERNAL_CACHE = 'external-cache-v1';
6+
const GITHUB_API_CACHE = 'github-api-cache-v3'; // Updated version
7+
const STATIC_CACHE = 'static-cache-v3';
8+
const EXTERNAL_CACHE = 'external-cache-v2';
99
const TOKEN_STORE = 'github-token-store';
1010
const STATIC_ASSETS = ['/', '/index.html', '/sw.js'];
1111

@@ -27,22 +27,24 @@ self.addEventListener('install', (event) => {
2727
self.skipWaiting();
2828
});
2929

30-
// Activate event - clean up old caches
30+
// Activate event - clean up old caches and take control
3131
self.addEventListener('activate', (event) => {
3232
console.log('Service Worker activating.');
3333
event.waitUntil(
34-
caches.keys().then(cacheNames => {
35-
return Promise.all(
36-
cacheNames.map(cacheName => {
37-
if (cacheName !== GITHUB_API_CACHE && cacheName !== STATIC_CACHE && cacheName !== EXTERNAL_CACHE) {
38-
console.log('Deleting old cache:', cacheName);
39-
return caches.delete(cacheName);
40-
}
41-
})
42-
);
43-
})
34+
Promise.all([
35+
caches.keys().then(cacheNames => {
36+
return Promise.all(
37+
cacheNames.map(cacheName => {
38+
if (cacheName !== GITHUB_API_CACHE && cacheName !== STATIC_CACHE && cacheName !== EXTERNAL_CACHE) {
39+
console.log('Deleting old cache:', cacheName);
40+
return caches.delete(cacheName);
41+
}
42+
})
43+
);
44+
}),
45+
self.clients.claim()
46+
])
4447
);
45-
event.waitUntil(self.clients.claim());
4648
});
4749

4850
// Fetch event - route requests appropriately
@@ -54,7 +56,7 @@ self.addEventListener('fetch', (event) => {
5456
return;
5557
}
5658

57-
// Handle all GitHub API requests
59+
// Handle all GitHub API requests with network-first strategy
5860
if (url.origin === GITHUB_API_BASE) {
5961
event.respondWith(handleGitHubRequest(event.request));
6062
return;
@@ -97,58 +99,63 @@ self.addEventListener('fetch', (event) => {
9799
}
98100
});
99101

100-
// Handle GitHub API requests - cache-first for offline support
102+
// Handle GitHub API requests - network-first for fresh data, cache for offline
101103
async function handleGitHubRequest(request) {
104+
const url = new URL(request.url);
105+
106+
// Check if this is a request for user posts/issues
107+
const isUserContentRequest = url.pathname.includes('/issues') ||
108+
url.pathname.includes('/posts') ||
109+
(url.pathname.includes('/repos/') && url.pathname.includes('/contents/'));
110+
102111
try {
103112
const token = await getStoredToken();
104113

105-
// For GET requests, check cache first
106-
if (request.method === 'GET') {
107-
const cache = await caches.open(GITHUB_API_CACHE);
108-
const cacheKey = new Request(request.url, {
109-
method: 'GET',
110-
headers: new Headers({ Accept: 'application/vnd.github+json' })
111-
});
112-
113-
const cachedResponse = await cache.match(cacheKey);
114-
if (cachedResponse) {
115-
return cachedResponse.clone();
116-
}
117-
}
118-
119114
// Create request with authorization token
120-
const authHeaders = new Headers(request.headers);
115+
const authHeaders = new Headers();
121116
authHeaders.set('Accept', 'application/vnd.github+json');
117+
authHeaders.set('Cache-Control', 'no-cache, no-store, must-revalidate');
118+
authHeaders.set('Pragma', 'no-cache');
122119

123120
if (token) {
124121
authHeaders.set('Authorization', `Bearer ${token}`);
125122
}
126123

127-
const authRequest = new Request(request, { headers: authHeaders });
124+
const authRequest = new Request(request, {
125+
headers: authHeaders,
126+
cache: 'no-store' // Don't use HTTP cache
127+
});
128+
129+
// Network-first strategy for fresh data
128130
const response = await fetch(authRequest);
129-
130-
// Cache successful GET responses
131+
132+
// Cache successful GET responses (especially important for posts)
131133
if (request.method === 'GET' && response.ok) {
132134
const cache = await caches.open(GITHUB_API_CACHE);
133-
cache.put(request.url, response.clone()).catch(err => {
134-
console.warn('Failed to cache response:', err);
135-
});
135+
// Clone response before caching
136+
const responseToCache = response.clone();
137+
await cache.put(request.url, responseToCache);
138+
console.log('Cached fresh response for:', request.url);
136139
}
137-
140+
138141
return response;
139142

140143
} catch (error) {
141144
console.error('GitHub API fetch error:', error.message);
142145

143-
// For GET requests, try offline fallback
146+
// For GET requests, try offline fallback from cache
144147
if (request.method === 'GET') {
145-
return tryOfflineFallback(request);
148+
const cachedResponse = await tryOfflineFallback(request);
149+
if (cachedResponse) {
150+
console.log('Returning cached data for:', request.url);
151+
return cachedResponse;
152+
}
146153
}
147154

148-
// For other methods (PUT, DELETE, etc), return offline error
155+
// Return offline error
149156
return new Response(JSON.stringify({
150157
error: 'Offline',
151-
message: 'No connection - offline mode'
158+
message: 'No connection - using cached data if available'
152159
}), {
153160
status: 503,
154161
statusText: 'Service Unavailable',
@@ -157,11 +164,11 @@ async function handleGitHubRequest(request) {
157164
}
158165
}
159166

160-
// Handle static requests - network-first, fallback to cache
167+
// Handle static requests - network-first with cache fallback
161168
async function handleStaticRequest(request) {
162169
try {
163170
const response = await fetch(request);
164-
if (response.ok) {
171+
if (response && response.status === 200) {
165172
const cache = await caches.open(STATIC_CACHE);
166173
cache.put(request, response.clone()).catch(err => {
167174
console.warn('Failed to cache response:', err);
@@ -172,9 +179,11 @@ async function handleStaticRequest(request) {
172179
} catch (error) {
173180
const cached = await caches.match(request);
174181
if (cached) {
182+
console.log('Returning cached static asset:', request.url);
175183
return cached;
176184
}
177185

186+
// Fallback to index.html for navigation requests
178187
const navigationFallback = request.mode === 'navigate' || (request.headers.get('accept')?.includes('text/html'));
179188
if (navigationFallback) {
180189
const shell = await caches.match('/index.html');
@@ -193,20 +202,12 @@ async function handleStaticRequest(request) {
193202
// Try to return cached version when offline
194203
async function tryOfflineFallback(request) {
195204
const cache = await caches.open(GITHUB_API_CACHE);
196-
const cached = await cache.match(request);
205+
const cached = await cache.match(request.url);
197206
if (cached) {
198207
console.log('Returning cached GitHub API response (offline)');
199208
return cached.clone();
200209
}
201-
202-
return new Response(JSON.stringify({
203-
error: 'Offline',
204-
message: 'No internet connection - data not available in cache'
205-
}), {
206-
status: 503,
207-
statusText: 'Service Unavailable',
208-
headers: { 'Content-Type': 'application/json' }
209-
});
210+
return null;
210211
}
211212

212213
// Get token from IndexedDB
@@ -236,10 +237,9 @@ async function storeToken(token) {
236237
await store.put({ id: 'github-token', token, timestamp: Date.now() });
237238

238239
// Notify clients that token was stored
239-
self.clients.matchAll().then(clients => {
240-
clients.forEach(client => {
241-
client.postMessage({ type: 'TOKEN_STORED' });
242-
});
240+
const clients = await self.clients.matchAll();
241+
clients.forEach(client => {
242+
client.postMessage({ type: 'TOKEN_STORED', token: token });
243243
});
244244

245245
console.log('Token stored securely in service worker');
@@ -248,6 +248,22 @@ async function storeToken(token) {
248248
}
249249
}
250250

251+
// Clear all caches and force refresh
252+
async function clearAllCaches() {
253+
const cacheNames = await caches.keys();
254+
await Promise.all(
255+
cacheNames.map(cacheName => caches.delete(cacheName))
256+
);
257+
console.log('All caches cleared');
258+
}
259+
260+
// Force refresh GitHub cache for specific endpoint
261+
async function refreshGitHubCache(url) {
262+
const cache = await caches.open(GITHUB_API_CACHE);
263+
await cache.delete(url);
264+
console.log('Cache cleared for:', url);
265+
}
266+
251267
// Open IndexedDB for token storage
252268
function openTokenDB() {
253269
return new Promise((resolve, reject) => {
@@ -265,27 +281,128 @@ function openTokenDB() {
265281
});
266282
}
267283

268-
// Message handling for token management
284+
// Message handling for token management and cache control
269285
self.addEventListener('message', (event) => {
270-
if (event.data && event.data.type === 'SET_TOKEN') {
271-
storeToken(event.data.token);
272-
} else if (event.data && event.data.type === 'CLEAR_CACHE') {
273-
Promise.all([
274-
caches.delete(GITHUB_API_CACHE),
275-
caches.delete(STATIC_CACHE)
276-
]).then(() => {
277-
console.log('All service worker caches cleared');
286+
const { data } = event;
287+
288+
if (data && data.type === 'SET_TOKEN') {
289+
storeToken(data.token);
290+
}
291+
else if (data && data.type === 'CLEAR_CACHE') {
292+
clearAllCaches().then(() => {
278293
self.clients.matchAll().then(clients => {
279294
clients.forEach(client => {
280295
client.postMessage({ type: 'CACHE_CLEARED' });
281296
});
282297
});
283298
});
284-
} else if (event.data && event.data.type === 'CLEAR_TOKEN') {
299+
}
300+
else if (data && data.type === 'CLEAR_TOKEN') {
285301
openTokenDB().then(db => {
286302
const transaction = db.transaction([TOKEN_STORE], 'readwrite');
287303
const store = transaction.objectStore(TOKEN_STORE);
288304
store.delete('github-token');
289305
});
290306
}
307+
else if (data && data.type === 'REFRESH_CACHE') {
308+
refreshGitHubCache(data.url).then(() => {
309+
self.clients.matchAll().then(clients => {
310+
clients.forEach(client => {
311+
client.postMessage({ type: 'CACHE_REFRESHED', url: data.url });
312+
});
313+
});
314+
});
315+
}
316+
else if (data && data.type === 'FORCE_REFRESH') {
317+
// Force refresh all GitHub API caches
318+
caches.open(GITHUB_API_CACHE).then(cache => {
319+
cache.keys().then(keys => {
320+
keys.forEach(key => {
321+
cache.delete(key);
322+
});
323+
});
324+
}).then(() => {
325+
self.clients.matchAll().then(clients => {
326+
clients.forEach(client => {
327+
client.postMessage({ type: 'FORCE_REFRESH_COMPLETE' });
328+
});
329+
});
330+
});
331+
}
332+
else if (data && data.type === 'SKIP_WAITING') {
333+
self.skipWaiting();
334+
}
291335
});
336+
337+
// Background sync for offline posts
338+
self.addEventListener('sync', (event) => {
339+
if (event.tag === 'sync-posts') {
340+
event.waitUntil(syncPendingPosts());
341+
}
342+
});
343+
344+
async function syncPendingPosts() {
345+
console.log('Syncing pending posts...');
346+
const token = await getStoredToken();
347+
if (!token) return;
348+
349+
// Here you can implement retry logic for failed posts
350+
const db = await openPendingPostsDB();
351+
const pendingPosts = await getPendingPosts(db);
352+
353+
for (const post of pendingPosts) {
354+
try {
355+
const response = await fetch(post.url, {
356+
method: 'POST',
357+
headers: {
358+
'Authorization': `Bearer ${token}`,
359+
'Content-Type': 'application/json',
360+
'Accept': 'application/vnd.github+json'
361+
},
362+
body: JSON.stringify(post.data)
363+
});
364+
365+
if (response.ok) {
366+
await deletePendingPost(db, post.id);
367+
console.log('Post synced successfully:', post.id);
368+
}
369+
} catch (error) {
370+
console.error('Failed to sync post:', error);
371+
}
372+
}
373+
}
374+
375+
// Helper functions for pending posts storage
376+
function openPendingPostsDB() {
377+
return new Promise((resolve, reject) => {
378+
const request = indexedDB.open('PendingPostsDB', 1);
379+
request.onerror = () => reject(request.error);
380+
request.onsuccess = () => resolve(request.result);
381+
request.onupgradeneeded = (event) => {
382+
const db = event.target.result;
383+
if (!db.objectStoreNames.contains('pending-posts')) {
384+
db.createObjectStore('pending-posts', { keyPath: 'id', autoIncrement: true });
385+
}
386+
};
387+
});
388+
}
389+
390+
async function getPendingPosts(db) {
391+
return new Promise((resolve, reject) => {
392+
const transaction = db.transaction(['pending-posts'], 'readonly');
393+
const store = transaction.objectStore('pending-posts');
394+
const request = store.getAll();
395+
request.onsuccess = () => resolve(request.result || []);
396+
request.onerror = () => reject(request.error);
397+
});
398+
}
399+
400+
async function deletePendingPost(db, id) {
401+
return new Promise((resolve, reject) => {
402+
const transaction = db.transaction(['pending-posts'], 'readwrite');
403+
const store = transaction.objectStore('pending-posts');
404+
const request = store.delete(id);
405+
request.onsuccess = () => resolve();
406+
request.onerror = () => reject(request.error);
407+
});
408+
}

0 commit comments

Comments
 (0)