1/** 2 * ChurchOS Service Worker 3 * ì¤íë¼ì¸ ì§ì ë° ìºì± 4 */ 5 6const CACHE_NAME = 'churchos-v4' 7const STATIC_CACHE = 'churchos-static-v4' 8const DYNAMIC_CACHE = 'churchos-dynamic-v4' 9 10// ì ì ìì (íì ìºì) 11const STATIC_ASSETS = [ 12 '/', 13 '/index.html', 14 '/manifest.json', 15 '/pic/logo.png', 16] 17 18// API ê²½ë¡ í¨í´ (ìºìíì§ ìì) 19const API_PATTERNS = [ 20 /^https:\/\/firestore\.googleapis\.com/, 21 /^https:\/\/firebase\.googleapis\.com/, 22 /^https:\/\/identitytoolkit\.googleapis\.com/, 23 /^https:\/\/securetoken\.googleapis\.com/, 24] 25 26// ìºì ì°ì ê²½ë¡ í¨í´ 27const CACHE_FIRST_PATTERNS = [ 28 /\.(?:png|jpg|jpeg|gif|svg|webp|ico)$/, 29 /\.(?:woff2?|ttf|otf|eot)$/, 30 /\.(?:css|js)$/, 31] 32 33// ============================================ 34// ì¤ì¹ ì´ë²¤í¸ 35// ============================================ 36self.addEventListener('install', (event) => { 37 console.log('[SW] Installing Service Worker...') 38 39 event.waitUntil( 40 caches.open(STATIC_CACHE) 41 .then((cache) => { 42 console.log('[SW] Caching static assets') 43 return cache.addAll(STATIC_ASSETS) 44 }) 45 .then(() => { 46 console.log('[SW] Installation complete') 47 return self.skipWaiting() 48 }) 49 .catch((error) => { 50 console.error('[SW] Installation failed:', error) 51 }) 52 ) 53}) 54 55// ============================================ 56// íì±í ì´ë²¤í¸ 57// ============================================ 58self.addEventListener('activate', (event) => { 59 console.log('[SW] Activating Service Worker...') 60 61 event.waitUntil( 62 caches.keys() 63 .then((cacheNames) => { 64 return Promise.all( 65 cacheNames 66 .filter((name) => { 67 // íì¬ ë²ì ì´ ìë ìºì ìì 68 return name.startsWith('churchos-') && 69 name !== STATIC_CACHE && 70 name !== DYNAMIC_CACHE 71 }) 72 .map((name) => { 73 console.log('[SW] Deleting old cache:', name) 74 return caches.delete(name) 75 }) 76 ) 77 }) 78 .then(() => { 79 console.log('[SW] Activation complete') 80 return self.clients.claim() 81 }) 82 ) 83}) 84 85// ============================================ 86// Fetch ì´ë²¤í¸ 87// ============================================ 88self.addEventListener('fetch', (event) => { 89 const { request } = event 90 const url = new URL(request.url) 91 92 // http/httpsê° ìë ìì²ì 무ì (chrome-extension:// ë±) 93 if (url.protocol !== 'http:' && url.protocol !== 'https:') { 94 return 95 } 96 97 // ì¸ë¶ CDN ìì²ì ìë¹ì¤ ìì»¤ê° ì²ë¦¬íì§ ìì (ë¸ë¼ì°ì ê° ì§ì ì²ë¦¬) 98 const externalCDNs = [ 99 'cdn.tailwindcss.com', 100 'www.gstatic.com', 101 'gstatic.com', 102 'cdnjs.cloudflare.com', 103 'cdn.jsdelivr.net', 104 'fonts.googleapis.com', 105 'fonts.gstatic.com', 106 'apis.google.com' 107 ] 108 if (externalCDNs.some(cdn => url.hostname.includes(cdn))) { 109 return 110 } 111 112 // API ìì²ì ë¤í¸ìí¬ ì°ì 113 if (isApiRequest(url.href)) { 114 event.respondWith(networkFirst(request)) 115 return 116 } 117 118 // ì ì ììì ìºì ì°ì 119 if (isCacheFirstRequest(url.href)) { 120 event.respondWith(cacheFirst(request)) 121 return 122 } 123 124 // íì´ì§ ë¤ë¹ê²ì´ì ì ë¤í¸ìí¬ ì°ì + ì¤íë¼ì¸ í´ë°± 125 if (request.mode === 'navigate') { 126 event.respondWith(networkFirstWithFallback(request)) 127 return 128 } 129 130 // 기í ìì²ì Stale-While-Revalidate 131 event.respondWith(staleWhileRevalidate(request)) 132}) 133 134// ============================================ 135// ìºì± ì ëµ 136// ============================================ 137 138/** 139 * ìºì ì°ì ì ëµ (ì ì ìì) 140 */ 141async function cacheFirst(request) { 142 const cached = await caches.match(request) 143 if (cached) { 144 return cached 145 } 146 147 try { 148 const response = await fetch(request) 149 if (response.ok) { 150 const cache = await caches.open(STATIC_CACHE) 151 cache.put(request, response.clone()) 152 } 153 return response 154 } catch (error) {
155 console.error('[SW] Cache first failed:', error) 156 return new Response('Offline', { status: 503 }) 157 } 158} 159 160/** 161 * ë¤í¸ìí¬ ì°ì ì ëµ (API) 162 */ 163async function networkFirst(request) { 164 try { 165 const response = await fetch(request) 166 return response 167 } catch (error) { 168 const cached = await caches.match(request) 169 if (cached) { 170 return cached 171 } 172 return new Response(JSON.stringify({ error: 'Offline' }), { 173 status: 503, 174 headers: { 'Content-Type': 'application/json' } 175 }) 176 } 177} 178 179/** 180 * ë¤í¸ìí¬ ì°ì + ì¤íë¼ì¸ í´ë°± (íì´ì§) 181 */ 182async function networkFirstWithFallback(request) { 183 try { 184 // HTTP ìºì를 ì°íí´ íì ìµì index.htmlì ë°ìì¨ë¤ (ì ë°°í¬ ì¦ì ë°ì) 185 const response = await fetch(request, { cache: 'reload' }) 186 if (response.ok) { 187 const cache = await caches.open(DYNAMIC_CACHE) 188 cache.put(request, response.clone()) 189 } 190 return response 191 } catch (error) { 192 const cached = await caches.match(request) 193 if (cached) { 194 return cached 195 } 196 197 // ì¤íë¼ì¸ í´ë°± íì´ì§ 198 const fallbackResponse = await caches.match('/index.html') 199 if (fallbackResponse) { 200 return fallbackResponse 201 } 202 203 return new Response(getOfflinePage(), { 204 status: 503, 205 headers: { 'Content-Type': 'text/html; charset=utf-8' } 206 }) 207 } 208} 209 210/** 211 * Stale-While-Revalidate ì ëµ 212 */ 213async function staleWhileRevalidate(request) { 214 const cached = await caches.match(request) 215 216 const fetchPromise = fetch(request) 217 .then(async (response) => { 218 if (response.ok) { 219 try { 220 const responseClone = response.clone() 221 const cache = await caches.open(DYNAMIC_CACHE) 222 await cache.put(request, responseClone) 223 } catch (e) { 224 console.log('[SW] Cache put failed:', e) 225 } 226 } 227 return response 228 }) 229 .catch(() => cached) 230 231 return cached || fetchPromise 232} 233 234// ============================================ 235// í¬í¼ í¨ì 236// ============================================ 237 238function isApiRequest(url) { 239 return API_PATTERNS.some((pattern) => pattern.test(url)) 240} 241 242function isCacheFirstRequest(url) { 243 return CACHE_FIRST_PATTERNS.some((pattern) => pattern.test(url)) 244} 245 246function getOfflinePage() { 247 return ` 248 <!DOCTYPE html> 249 <html lang="ko"> 250 <head> 251 <meta charset="UTF-8"> 252 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 253 <title>ì¤íë¼ì¸ - ChurchOS</title> 254 <style> 255 * { margin: 0; padding: 0; box-sizing: border-box; } 256 body { 257 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 258 background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%); 259 min-height: 100vh; 260 display: flex; 261 align-items: center; 262 justify-content: center; 263 color: white; 264 text-align: center; 265 padding: 20px; 266 } 267 .container { 268 max-width: 400px; 269 } 270 .icon { 271 font-size: 64px; 272 margin-bottom: 24px; 273 } 274 h1 { 275 font-size: 24px; 276 margin-bottom: 12px; 277 } 278 p { 279 font-size: 16px; 280 opacity: 0.9; 281 margin-bottom: 24px; 282 line-height: 1.5; 283 } 284 button { 285 background: white; 286 color: #1e3a5f; 287 border: none; 288 padding: 12px 24px; 289 border-radius: 8px; 290 font-size: 16px; 291 font-weight: 600; 292 cursor: pointer; 293 transition: transform 0.2s; 294 } 295 button:hover { 296 transform: scale(1.05); 297 } 298 </style> 299 </head> 300 <body> 301 <div class="container"> 302 <div class="icon">ð¡</div> 303 <h1>ì¸í°ë· ì°ê²° ìì</h1> 304 <p>íì¬ ì¤íë¼ì¸ ìíì ëë¤. ì¸í°ë· ì°ê²°ì íì¸íê³ ë¤ì ìëí´ ì£¼ì¸ì.</p> 305 <button onclick="location.reload()">ë¤ì ìë</button> 306 </div> 307 </body> 308 </html> 309 ` 310} 311 312// ============================================
313// ë©ìì§ ì´ë²¤í¸ (ì±ê³¼ íµì ) 314// ============================================ 315self.addEventListener('message', (event) => { 316 if (event.data && event.data.type === 'SKIP_WAITING') { 317 self.skipWaiting() 318 } 319 320 if (event.data && event.data.type === 'CLEAR_CACHE') { 321 event.waitUntil( 322 caches.keys().then((names) => { 323 return Promise.all(names.map((name) => caches.delete(name))) 324 }) 325 ) 326 } 327}) 328 329console.log('[SW] Service Worker loaded')
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.