1- // Service Worker for API request proxying
2- // This intercepts GitHub API requests and adds authorization headers
3- // Token is stored securely in IndexedDB and retrieved by the worker
1+ // Service Worker for offline-first support
2+ // Caches static assets and GitHub API responses for offline access
3+ // Token is stored securely in IndexedDB
44
55const GITHUB_API_BASE = 'https://api.github.com' ;
6- const CACHE_NAME = 'github-api-cache-v1' ;
6+ const GITHUB_API_CACHE = 'github-api-cache-v2' ;
7+ const STATIC_CACHE = 'static-cache-v2' ;
78const TOKEN_STORE = 'github-token-store' ;
89
9- // Install event
10+ // Install event - cache essential static assets
1011self . addEventListener ( 'install' , ( event ) => {
1112 console . log ( 'Service Worker installing.' ) ;
13+ event . waitUntil (
14+ caches . open ( STATIC_CACHE ) . then ( cache => {
15+ console . log ( 'Caching static assets' ) ;
16+ const assets = [
17+ '/' ,
18+ '/index.html'
19+ ] ;
20+ return cache . addAll ( assets ) . catch ( err => {
21+ console . warn ( 'Some static assets failed to cache:' , err ) ;
22+ } ) ;
23+ } )
24+ ) ;
1225 self . skipWaiting ( ) ;
1326} ) ;
1427
15- // Activate event
28+ // Activate event - clean up old caches
1629self . addEventListener ( 'activate' , ( event ) => {
1730 console . log ( 'Service Worker activating.' ) ;
1831 event . waitUntil (
1932 caches . keys ( ) . then ( cacheNames => {
2033 return Promise . all (
2134 cacheNames . map ( cacheName => {
22- if ( cacheName !== CACHE_NAME ) {
35+ if ( cacheName !== GITHUB_API_CACHE && cacheName !== STATIC_CACHE ) {
36+ console . log ( 'Deleting old cache:' , cacheName ) ;
2337 return caches . delete ( cacheName ) ;
2438 }
2539 } )
@@ -29,54 +43,109 @@ self.addEventListener('activate', (event) => {
2943 event . waitUntil ( self . clients . claim ( ) ) ;
3044} ) ;
3145
32- // Fetch event - intercept GitHub API requests
46+ // Fetch event - route requests appropriately
3347self . addEventListener ( 'fetch' , ( event ) => {
3448 const url = new URL ( event . request . url ) ;
3549
36- // Only intercept GitHub API requests
37- if ( url . origin === GITHUB_API_BASE ) {
50+ // Handle GitHub API GET requests
51+ if ( url . origin === GITHUB_API_BASE && event . request . method === 'GET' ) {
3852 event . respondWith ( handleGitHubRequest ( event . request ) ) ;
3953 }
54+ // Handle static assets with network-first strategy
55+ else if ( event . request . method === 'GET' ) {
56+ event . respondWith ( handleStaticRequest ( event . request ) ) ;
57+ }
4058} ) ;
4159
42- // Handle GitHub API requests
60+ // Handle GitHub API requests - cache-first for offline support
4361async function handleGitHubRequest ( request ) {
4462 try {
45- // Get the token from IndexedDB
4663 const token = await getStoredToken ( ) ;
47-
4864 if ( ! token ) {
49- console . warn ( 'No token available for GitHub API request ' ) ;
50- return fetch ( request ) ;
65+ console . warn ( 'No token available' ) ;
66+ return tryOfflineFallback ( request ) ;
5167 }
5268
53- // Create new request with authorization header
69+ const cache = await caches . open ( GITHUB_API_CACHE ) ;
70+ const cacheKey = new Request ( request . url , {
71+ method : request . method ,
72+ headers : new Headers ( { Accept : 'application/vnd.github+json' } )
73+ } ) ;
74+
75+ // Check cache first for offline support
76+ const cachedResponse = await cache . match ( cacheKey ) ;
77+ if ( cachedResponse ) {
78+ return cachedResponse . clone ( ) ;
79+ }
80+
81+ // Create request with authorization
5482 const authHeaders = new Headers ( request . headers ) ;
5583 authHeaders . set ( 'Authorization' , `Bearer ${ token } ` ) ;
5684 authHeaders . set ( 'Accept' , 'application/vnd.github+json' ) ;
5785
58- const authRequest = new Request ( request , {
59- headers : authHeaders
60- } ) ;
61-
62- // Make the request
86+ const authRequest = new Request ( request , { headers : authHeaders } ) ;
6387 const response = await fetch ( authRequest ) ;
6488
65- // Return response (headers are sanitized by browser automatically)
89+ // Cache successful responses
90+ if ( response . ok ) {
91+ const responseClone = response . clone ( ) ;
92+ await cache . put ( cacheKey , responseClone ) ;
93+ }
94+
6695 return response ;
6796
6897 } catch ( error ) {
69- console . error ( 'Service Worker fetch error:' , error . message ) ;
70- return new Response ( JSON . stringify ( {
71- error : 'Network error' ,
72- message : 'Failed to complete request'
73- } ) , {
74- status : 500 ,
75- headers : { 'Content-Type' : 'application/json' }
98+ console . error ( 'GitHub API fetch error:' , error . message ) ;
99+ return tryOfflineFallback ( request ) ;
100+ }
101+ }
102+
103+ // Handle static requests - network-first, fallback to cache
104+ async function handleStaticRequest ( request ) {
105+ try {
106+ const response = await fetch ( request ) ;
107+ if ( response . ok ) {
108+ // Cache successful responses
109+ const cache = await caches . open ( STATIC_CACHE ) ;
110+ cache . put ( request , response . clone ( ) ) . catch ( err => {
111+ console . warn ( 'Failed to cache response:' , err ) ;
112+ } ) ;
113+ return response ;
114+ }
115+ throw new Error ( 'Network response not ok' ) ;
116+ } catch ( error ) {
117+ // Fall back to cache
118+ const cached = await caches . match ( request ) ;
119+ if ( cached ) {
120+ return cached ;
121+ }
122+
123+ return new Response ( 'Offline - Resource unavailable' , {
124+ status : 503 ,
125+ statusText : 'Service Unavailable'
76126 } ) ;
77127 }
78128}
79129
130+ // Try to return cached version when offline
131+ async function tryOfflineFallback ( request ) {
132+ const cache = await caches . open ( GITHUB_API_CACHE ) ;
133+ const cached = await cache . match ( request ) ;
134+ if ( cached ) {
135+ console . log ( 'Returning cached GitHub API response (offline)' ) ;
136+ return cached . clone ( ) ;
137+ }
138+
139+ return new Response ( JSON . stringify ( {
140+ error : 'Offline' ,
141+ message : 'No internet connection - data not available in cache'
142+ } ) , {
143+ status : 503 ,
144+ statusText : 'Service Unavailable' ,
145+ headers : { 'Content-Type' : 'application/json' }
146+ } ) ;
147+ }
148+
80149// Get token from IndexedDB
81150async function getStoredToken ( ) {
82151 try {
@@ -137,12 +206,15 @@ function openTokenDB() {
137206self . addEventListener ( 'message' , ( event ) => {
138207 if ( event . data && event . data . type === 'SET_TOKEN' ) {
139208 storeToken ( event . data . token ) ;
140- } else if ( event . data && event . data . type === 'CLEAR_TOKEN' ) {
141- // Clear token from storage
142- openTokenDB ( ) . then ( db => {
143- const transaction = db . transaction ( [ TOKEN_STORE ] , 'readwrite' ) ;
144- const store = transaction . objectStore ( TOKEN_STORE ) ;
145- store . delete ( 'github-token' ) ;
209+ } else if ( event . data && event . data . type === 'CLEAR_CACHE' ) {
210+ Promise . all ( [
211+ caches . delete ( GITHUB_API_CACHE ) ,
212+ caches . delete ( STATIC_CACHE )
213+ ] ) . then ( ( ) => {
214+ console . log ( 'All service worker caches cleared' ) ;
215+ self . clients . matchAll ( ) . then ( clients => {
216+ clients . forEach ( client => {
217+ client . postMessage ( { type : 'CACHE_CLEARED' } ) ;
218+ } ) ;
219+ } ) ;
146220 } ) ;
147- }
148- } ) ;
0 commit comments