From 294e33b5b3ecb2f1fc40b04b6619c05b9a37bf0b Mon Sep 17 00:00:00 2001 From: chpark Date: Sat, 30 May 2026 22:51:04 +0900 Subject: [PATCH] =?UTF-8?q?fix(push):=20=EC=83=81=EB=8B=A8=20=EB=B0=B0?= =?UTF-8?q?=EB=84=88=20=ED=91=9C=EC=8B=9C=20=EA=B0=95=ED=99=94=20+=20?= =?UTF-8?q?=EC=B2=AB=20=EC=A7=84=EC=9E=85=20=EC=9E=90=EB=8F=99=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20prompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service Worker 알림 옵션 강화 (sw.js v3): - requireInteraction: 사용자 dismiss 전까지 유지 (heads-up 보장) - vibrate: 안드로이드가 high-priority 채널로 분류 → 상단 배너 - silent: false 명시 (Samsung 버전 차이 대응) - renotify: 같은 tag 라도 다시 알림 - timestamp 명시 (Samsung 알림 정렬 안정) → 삼성 인터넷/Galaxy 에서 상단 배너 안 뜨고 사이트 알림 내역에만 쌓이던 문제 해소 회원정보 카드 마운트 자동 권한 prompt: - Notification.permission === 'default' 일 때 한 번만 자동 turnOn 시도 - APK 새 설치 후 첫 실행 = 안드로이드 13+ POST_NOTIFICATIONS prompt 자동 표시 - sessionStorage 로 같은 탭 안 반복 방지 Co-Authored-By: Claude Opus 4.7 --- public/sw.js | 12 +++++++++++- src/components/push-optin.tsx | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/public/sw.js b/public/sw.js index 47de9ba..7d5af01 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ // 모모유통 ERP — Service Worker (PWA install criteria 충족용) -const CACHE = 'momo-erp-v2'; +const CACHE = 'momo-erp-v3'; const PRECACHE = ['/', '/manifest.json', '/icon-192.png', '/icon-512.png', '/badge-96.png']; self.addEventListener('install', (e) => { @@ -27,6 +27,11 @@ self.addEventListener('fetch', (e) => { }); // ===== 웹 푸시 ===== +// 삼성 인터넷/Samsung Galaxy 에서 상단 배너(heads-up) 가 안 뜨던 문제 해결: +// - vibrate: 패턴 명시 (안드로이드가 high-priority 채널로 분류) +// - requireInteraction: 사용자가 직접 닫을 때까지 유지 +// - renotify: 같은 tag 라도 다시 알림 +// - silent: false 명시 (Samsung 일부 버전에서 기본값이 true 인 케이스 회피) self.addEventListener('push', (e) => { let data = {}; try { data = e.data ? e.data.json() : {}; } catch (_) { data = {}; } @@ -36,6 +41,11 @@ self.addEventListener('push', (e) => { icon: data.icon || '/icon-192.png', // 큰 아이콘 = 모모 로고(초록 M) badge: data.badge || '/badge-96.png', // 상태바 작은 아이콘 = 흰 M 단색(투명 배경) tag: data.tag || undefined, + renotify: !!data.tag, + requireInteraction: true, + silent: false, + vibrate: [200, 100, 200], + timestamp: Date.now(), data: { url: data.url || '/m/orders/new' }, }; e.waitUntil(self.registration.showNotification(title, options)); diff --git a/src/components/push-optin.tsx b/src/components/push-optin.tsx index 942a2ec..afbff47 100644 --- a/src/components/push-optin.tsx +++ b/src/components/push-optin.tsx @@ -132,6 +132,7 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) { }; }, [supported]); + // 마운트 시 상태 동기화 useEffect(() => { if (!supported || bootRef.current) return; @@ -271,6 +272,19 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) { } }; + // 첫 진입 자동 권한 prompt — Notification.permission === 'default' 이면 한 번만 시도. + // APK 새 설치 직후 첫 실행이면 안드로이드 13+ POST_NOTIFICATIONS prompt 가 자동으로 뜬다. + // 이미 한 번 prompt 받았던 디바이스는 default 가 아니므로 prompt 재표시 없음 (silent). + useEffect(() => { + if (!supported) return; + if (typeof Notification === "undefined" || Notification.permission !== "default") return; + const KEY = "momo-push-prompted"; + try { if (sessionStorage.getItem(KEY)) return; } catch { /* ignore */ } + try { sessionStorage.setItem(KEY, "1"); } catch { /* ignore */ } + const t = setTimeout(() => { turnOn().catch(() => {}); }, 800); + return () => clearTimeout(t); + }, [supported, turnOn]); + // === 작은 헤더 토글 === if (!supported || !httpsOK) { if (variant !== "card") return null;