fix(push-optin): 삼성 인터넷 지원 + ArrayBuffer applicationServerKey

원인 1: applicationServerKey 타입 호환성
- Samsung Internet 은 Uint8Array 거부, ArrayBuffer 만 허용 (Chrome 은 둘 다 OK)
- urlBase64ToUint8Array(publicKey).buffer 로 ArrayBuffer 명시 전달

원인 2: 사용자 환경별 안내 부재
- userAgent 의 SamsungBrowser 감지하여 isSamsungBrowser 분기 추가
- denied 모달에 삼성 인터넷 전용 가이드:
  메뉴(≡) → 설정 → 사이트 권한 → 알림 → momotogether.com 허용
- 카드 하단 환경 진단에 '삼성 인터넷' / '안드로이드(Chrome)' 구분 표시

원인 3: 푸시 구독 실패 메시지 모호
- pushManager.subscribe 에러 메시지에 사이트 권한 차단 가능성 힌트 추가

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
chpark
2026-05-30 22:09:12 +09:00
parent 0dd392136b
commit f2e7c03507
+24 -5
View File
@@ -92,12 +92,21 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) {
const { publicKey } = await res.json();
if (!publicKey) throw new Error("서버가 VAPID 공개키를 반환하지 않았습니다.");
try {
// Samsung Internet 은 ArrayBuffer 만 받음 (Uint8Array 그대로 넣으면 거부).
// Chrome 도 ArrayBuffer 받으므로 .buffer 로 통일.
const keyBuf = urlBase64ToUint8Array(publicKey).buffer as ArrayBuffer;
sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey) as BufferSource,
applicationServerKey: keyBuf,
});
} catch (e) {
throw new Error(`푸시 구독 실패: ${(e as Error).message ?? e}`);
const msg = (e as Error).message ?? String(e);
// Samsung Internet 특유 메시지 변환
let hint = "";
if (/abort|denied|NotAllowed/i.test(msg)) {
hint = " (브라우저 설정에서 알림 차단됨 가능성 — 사이트 권한 확인)";
}
throw new Error(`푸시 구독 실패: ${msg}${hint}`);
}
}
@@ -294,10 +303,11 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) {
}
// === card 변형 (회원정보 페이지) ===
// denied 환경 감지 — TWA 앱은 안드로이드 OS 설정, 브라우저는 사이트 설정에서 풀어야
// denied 환경 감지 — TWA / Samsung Internet / Chrome / iOS 별 안내 분기
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
const isAndroid = /Android/i.test(ua);
const isiOS = /iPhone|iPad|iPod/i.test(ua);
const isSamsungBrowser = /SamsungBrowser/i.test(ua);
const isTWA = isAndroid && (/wv|; ?Trusted Web Activity/i.test(ua) || (typeof document !== "undefined" && (document.referrer ?? "").startsWith("android-app://")));
return (
@@ -330,7 +340,16 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) {
<div className="font-bold text-rose-700 inline-flex items-center gap-1">
<AlertCircle size={13} />
</div>
{isTWA || isAndroid ? (
{isSamsungBrowser ? (
<ol className="list-decimal list-inside leading-relaxed space-y-0.5 text-slate-600 pl-1">
<li><b> </b> <b> ()</b> <b></b></li>
<li><b> </b> <b> </b> <b></b></li>
<li><b></b> <b>momotogether.com</b> <b></b> <b></b></li>
<li> <b> </b> momotogether.com </li>
<li> </li>
<li> 되면: 삼성 <b> </b> </li>
</ol>
) : isTWA || isAndroid ? (
<ol className="list-decimal list-inside leading-relaxed space-y-0.5 text-slate-600 pl-1">
<li> <b></b> </li>
<li><b> </b> ( ) <b></b></li>
@@ -362,7 +381,7 @@ export function PushOptIn({ variant = "compact" }: PushOptInProps) {
· : <b className={denied ? "text-rose-500" : "text-slate-600"}>{typeof Notification !== "undefined" ? Notification.permission : "?"}</b>
{" · "}: <b className={httpsOK ? "text-emerald-600" : "text-rose-500"}>{httpsOK ? "OK" : "NO"}</b>
{" · "}SW: <b>{"serviceWorker" in (typeof navigator !== "undefined" ? navigator : ({} as Navigator)) ? "지원" : "미지원"}</b>
{" · "}: <b>{isTWA ? "TWA앱" : isAndroid ? "안드로이드" : isiOS ? "iOS" : "데스크탑/기타"}</b>
{" · "}: <b>{isTWA ? "TWA앱" : isSamsungBrowser ? "삼성 인터넷" : isAndroid ? "안드로이드(Chrome)" : isiOS ? "iOS" : "데스크탑/기타"}</b>
</div>
</div>
);