33// Token is stored securely in IndexedDB
44
55const 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 ' ;
99const TOKEN_STORE = 'github-token-store' ;
1010const 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
3131self . 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
101103async 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
161168async 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
194203async 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
252268function 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
269285self . 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