import { get, post } from "./api"; function b64ToBytes(b64: string): Uint8Array { const s = atob(b64.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((b64.length + 3) % 4)); const out = new Uint8Array(new ArrayBuffer(s.length)); for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i); return out; } export const pushSupported = () => "serviceWorker" in navigator && "PushManager" in window && "Notification" in window; /** Current push subscription of this device (null if none). */ export async function currentSubscription(): Promise { if (!pushSupported()) return null; const reg = await navigator.serviceWorker.ready; return reg.pushManager.getSubscription(); } /** Ask for permission, subscribe this device and register it with the server. */ export async function enablePush(): Promise { if (!pushSupported()) throw new Error("Push is not supported by this browser (on iOS: install the app to the home screen first)."); const perm = await Notification.requestPermission(); if (perm !== "granted") throw new Error("Notifications were not allowed."); const reg = await navigator.serviceWorker.ready; const { key } = await get<{ key: string }>("/push/key"); let sub = await reg.pushManager.getSubscription(); // A subscription for another server key must be replaced. if (sub && sub.options.applicationServerKey) { const cur = new Uint8Array(sub.options.applicationServerKey); const want = b64ToBytes(key); if (cur.length !== want.length || cur.some((b, i) => b !== want[i])) { await sub.unsubscribe(); sub = null; } } if (!sub) sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: b64ToBytes(key) }); await post("/push/subscribe", sub.toJSON()); localStorage.setItem("codecraft.push", "1"); } export async function disablePush(): Promise { const sub = await currentSubscription(); if (sub) { await fetch("/api/push/subscribe", { method: "DELETE", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ endpoint: sub.endpoint }) }); await sub.unsubscribe(); } localStorage.removeItem("codecraft.push"); } /** Re-register this device's existing subscription with the server (idempotent). */ export async function syncPush(): Promise { const sub = await currentSubscription().catch(() => null); if (!sub) return false; await post("/push/subscribe", sub.toJSON()); localStorage.setItem("codecraft.push", "1"); return true; } export async function testPush(): Promise<{ sent: number; errors: string[] }> { // Make sure the server knows this device before testing. await syncPush(); return post<{ sent: number; errors: string[] }>("/push/test", {}); }