/// import { clientsClaim } from 'workbox-core'; import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'; import { NavigationRoute, registerRoute } from 'workbox-routing'; clientsClaim(); cleanupOutdatedCaches(); const manifest = self.__WB_MANIFEST; if (Array.isArray(manifest) && manifest.length > 0) { precacheAndRoute(manifest); const navigationHandler = createHandlerBoundToURL('/index.html'); registerRoute(new NavigationRoute(navigationHandler)); } else { registerRoute( new NavigationRoute(({ request }) => { return fetch(request); }), ); } self.addEventListener('message', (event) => { if (event.data?.type === 'SKIP_WAITING') { void self.skipWaiting(); } }); self.addEventListener('push', (event) => { if (!event.data) { return; } let payload = {}; try { payload = event.data.json(); } catch { payload = { title: '알림', body: event.data.text(), data: {}, }; } const title = payload.title || 'AI Code App'; const body = payload.body || '새 알림이 도착했습니다.'; const notificationScope = payload.data?.notificationScope; const notificationKey = payload.data?.notificationKey || [payload.threadId ?? 'ai-code-app-notification', payload.data?.eventType ?? 'event', Date.now()].join(':'); event.waitUntil( self.registration.getNotifications().then((notifications) => { if (notificationScope === 'automation') { notifications .filter((notification) => notification.data?.notificationScope === 'automation') .forEach((notification) => notification.close()); } return self.registration.showNotification(title, { body, data: payload.data ?? {}, tag: notificationKey, badge: '/pwa-192x192.svg', icon: '/pwa-192x192.svg', }); }), ); }); self.addEventListener('notificationclick', (event) => { const notificationData = event.notification.data ?? {}; const notificationSessionId = typeof notificationData.sessionId === 'string' ? notificationData.sessionId.trim() : ''; const notificationCategory = typeof notificationData.category === 'string' ? notificationData.category.trim() : ''; const notificationType = typeof notificationData.type === 'string' ? notificationData.type.trim() : ''; const notificationThreadId = typeof notificationData.threadId === 'string' ? notificationData.threadId.trim() : ''; const isChatNotification = notificationCategory === 'chat' || notificationType.startsWith('chat') || notificationThreadId.startsWith('chat:'); event.notification.close(); let targetUrl; try { targetUrl = notificationData.targetUrl ? new URL(String(notificationData.targetUrl)) : new URL('/', self.location.origin); } catch { targetUrl = new URL('/', self.location.origin); } if (targetUrl.origin !== self.location.origin) { const fallbackUrl = new URL('/', self.location.origin); targetUrl.searchParams.forEach((value, key) => { fallbackUrl.searchParams.set(key, value); }); targetUrl = fallbackUrl; } if (!targetUrl.searchParams.has('topMenu')) { targetUrl.searchParams.set('topMenu', notificationData.category === 'chat' ? 'chat' : 'plans'); } if (isChatNotification) { targetUrl.pathname = '/chat/live'; targetUrl.searchParams.set('topMenu', 'chat'); targetUrl.searchParams.delete('chatView'); targetUrl.searchParams.delete('runtimeRequestId'); if (notificationSessionId) { targetUrl.searchParams.set('sessionId', notificationSessionId); } } if (notificationData.planId && !targetUrl.searchParams.has('planId')) { targetUrl.searchParams.set('planId', String(notificationData.planId)); } if (notificationData.workId && !targetUrl.searchParams.has('workId')) { targetUrl.searchParams.set('workId', String(notificationData.workId)); } event.waitUntil( Promise.all([ self.registration.getNotifications().then((notifications) => { if (!notificationSessionId || !isChatNotification) { return; } notifications.forEach((notification) => { const candidateData = notification.data && typeof notification.data === 'object' ? notification.data : null; const candidateSessionId = candidateData && typeof candidateData.sessionId === 'string' ? candidateData.sessionId.trim() : ''; const candidateCategory = candidateData && typeof candidateData.category === 'string' ? candidateData.category.trim() : ''; const candidateType = candidateData && typeof candidateData.type === 'string' ? candidateData.type.trim() : ''; const candidateThreadId = candidateData && typeof candidateData.threadId === 'string' ? candidateData.threadId.trim() : ''; const isCandidateChatNotification = candidateCategory === 'chat' || candidateType.startsWith('chat') || candidateThreadId.startsWith('chat:'); if (isCandidateChatNotification && candidateSessionId === notificationSessionId) { notification.close(); } }); }), self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { const existingClient = clientList[0]; if (existingClient) { existingClient.focus(); try { const clientUrl = new URL(existingClient.url); if (clientUrl.origin === targetUrl.origin) { return existingClient.navigate(targetUrl.toString()); } } catch { return existingClient.navigate(targetUrl.toString()); } } return self.clients.openWindow(targetUrl.toString()); }), ]), ); });