From d5b8134b9df0528c64ab2ee645140fc7e171a67f Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Sat, 7 Mar 2026 15:19:06 +0000 Subject: [PATCH] Published your App Replit-Commit-Author: Deployment Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: f475857c-708a-4080-a0a0-ef301e469c0a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/ICRgny1 Replit-Commit-Deployment-Build-Id: 19726b10-774b-4c9e-8a7a-421a5d549a3a Replit-Helium-Checkpoint-Created: true --- client/src/lib/push-utils.ts | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 client/src/lib/push-utils.ts diff --git a/client/src/lib/push-utils.ts b/client/src/lib/push-utils.ts new file mode 100644 index 0000000..04bf346 --- /dev/null +++ b/client/src/lib/push-utils.ts @@ -0,0 +1,63 @@ +function urlBase64ToUint8Array(base64String: string) { + const padding = "=".repeat((4 - (base64String.length % 4)) % 4); + const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} + +export function isPushSupported(): boolean { + return "serviceWorker" in navigator && "PushManager" in window; +} + +export async function getExistingSubscription(): Promise { + if (!isPushSupported()) return null; + try { + const reg = await navigator.serviceWorker.getRegistration("/sw.js"); + if (reg) return await reg.pushManager.getSubscription(); + } catch {} + return null; +} + +export async function subscribeToPush(): Promise { + const keyRes = await fetch("/api/push/vapid-key"); + const { publicKey } = await keyRes.json(); + if (!publicKey) throw new Error("VAPID key not configured"); + + const reg = await navigator.serviceWorker.register("/sw.js"); + await navigator.serviceWorker.ready; + + const sub = await reg.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: urlBase64ToUint8Array(publicKey), + }); + + const subJson = sub.toJSON(); + const res = await fetch("/api/push/subscribe", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ endpoint: subJson.endpoint, keys: subJson.keys }), + }); + if (!res.ok) throw new Error("Subscription save failed"); + return true; +} + +export async function unsubscribeFromPush(): Promise { + const reg = await navigator.serviceWorker.getRegistration("/sw.js"); + if (reg) { + const sub = await reg.pushManager.getSubscription(); + if (sub) { + const res = await fetch("/api/push/unsubscribe", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ endpoint: sub.endpoint }), + }); + if (!res.ok) throw new Error("Unsubscribe failed"); + await sub.unsubscribe(); + } + } + return true; +}