/* eslint-disable */ // Relay 서비스워커 — 웹 푸시(Level2): 사이트/탭을 닫아도 OS 토스트를 띄운다. // 푸시 페이로드는 백엔드 WebPushService 가 { title, body, url } 로 보낸다. self.addEventListener('install', () => { // 새 워커를 즉시 활성화 self.skipWaiting() }) self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()) }) self.addEventListener('push', (event) => { if (!event.data) return let payload = {} try { payload = event.data.json() } catch (e) { payload = { title: 'Relay', body: '새 알림이 도착했습니다' } } const title = payload.title || 'Relay' const body = payload.body || '' const url = payload.url || null event.waitUntil( (async () => { // 열린 클라이언트(탭)가 있으면 페이지의 SSE 가 이미 토스트를 처리 → 중복 표시 생략 const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true, }) if (clients.length > 0) return await self.registration.showNotification(title, { body, icon: '/relay-icon-256.png', badge: '/relay-icon-256.png', data: { url }, }) })(), ) }) // 브라우저가 푸시 구독을 자체 회전(만료·교체)시키면 발생 — 새 구독으로 재등록해 // 사용자가 앱을 다시 열지 않아도 푸시가 끊기지 않게 한다. best-effort(실패 시 앱 재진입의 // enablePush(true) 가 최신화). 백엔드 경로는 기본 프록시('/api') 기준. self.addEventListener('pushsubscriptionchange', (event) => { event.waitUntil( (async () => { try { // 브라우저가 새 구독을 넘겨줬으면 그대로, 아니면 이전 구독의 VAPID 키로 재구독 let sub = event.newSubscription || null if (!sub) { const opts = event.oldSubscription && event.oldSubscription.options ? event.oldSubscription.options : null const appServerKey = opts && opts.applicationServerKey // 키가 없으면 재구독 불가 — 앱 재진입 때 복구되도록 조용히 종료 if (!appServerKey) return sub = await self.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: appServerKey, }) } if (!sub) return const json = sub.toJSON() await fetch('/api/notifications/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ endpoint: sub.endpoint, keys: { p256dh: (json.keys && json.keys.p256dh) || '', auth: (json.keys && json.keys.auth) || '', }, }), }) } catch (e) { /* 재구독/재등록 실패 무시 — 앱 재진입 시 최신화 */ } })(), ) }) self.addEventListener('notificationclick', (event) => { event.notification.close() const url = (event.notification.data && event.notification.data.url) || '/' event.waitUntil( (async () => { const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true, }) // 이미 열린 창이 있으면 포커스 후 해당 경로로 이동 for (const client of clients) { if ('focus' in client) { await client.focus() if ('navigate' in client && url) { try { await client.navigate(url) } catch (e) { /* 이동 실패 무시 */ } } return } } // 열린 창이 없으면 새 창 if (self.clients.openWindow) await self.clients.openWindow(url) })(), ) })