(function () { function urlBase64ToUint8Array(base64String) { const padding = "=".repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); const rawData = atob(base64); const output = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; i++) { output[i] = rawData.charCodeAt(i); } return output; } function isPushSupported() { return "serviceWorker" in navigator && "PushManager" in window; } async function getSubscriptionState() { if (!isPushSupported()) return "unsupported"; const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.getSubscription(); return sub ? "subscribed" : "unsubscribed"; } async function subscribePush() { if (!isPushSupported()) { alert("이 브라우저는 알림을 지원하지 않아요."); return false; } const permission = await Notification.requestPermission(); if (permission !== "granted") { alert("알림 권한이 허용되지 않았어요."); return false; } const reg = await navigator.serviceWorker.ready; const { publicKey } = await fetch("/api/push/vapid-public-key").then((r) => r.json()); const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }); await fetch("/api/push/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(sub.toJSON()), }); return true; } async function unsubscribePush() { if (!isPushSupported()) return; const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.getSubscription(); if (!sub) return; await fetch("/api/push/unsubscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ endpoint: sub.endpoint }), }); await sub.unsubscribe(); } window.habitPush = { getSubscriptionState, subscribePush, unsubscribePush }; })();