social overhaul
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
function urlBase64ToBuffer(base64String: string): ArrayBuffer {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
const raw = atob(base64);
|
||||
const buffer = new ArrayBuffer(raw.length);
|
||||
const output = new Uint8Array(buffer);
|
||||
for (let i = 0; i < raw.length; i++) output[i] = raw.charCodeAt(i);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export type PushPermission = "default" | "granted" | "denied";
|
||||
|
||||
export interface UseWebPush {
|
||||
supported: boolean;
|
||||
configured: boolean;
|
||||
permission: PushPermission;
|
||||
subscribed: boolean;
|
||||
busy: boolean;
|
||||
enable: () => Promise<boolean>;
|
||||
disable: () => Promise<boolean>;
|
||||
sendTest: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
async function getRegistration(): Promise<ServiceWorkerRegistration | null> {
|
||||
if (!("serviceWorker" in navigator)) return null;
|
||||
return (
|
||||
(await navigator.serviceWorker.getRegistration()) ??
|
||||
(await navigator.serviceWorker.ready.catch(() => null))
|
||||
);
|
||||
}
|
||||
|
||||
export function useWebPush(): UseWebPush {
|
||||
const [supported, setSupported] = useState(false);
|
||||
const [permission, setPermission] = useState<PushPermission>("default");
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const configured = Boolean(VAPID_PUBLIC_KEY);
|
||||
|
||||
useEffect(() => {
|
||||
const isSupported =
|
||||
typeof window !== "undefined" &&
|
||||
"Notification" in window &&
|
||||
"PushManager" in window &&
|
||||
"serviceWorker" in navigator;
|
||||
setSupported(isSupported);
|
||||
if (!isSupported) return;
|
||||
|
||||
setPermission(Notification.permission as PushPermission);
|
||||
|
||||
(async () => {
|
||||
const reg = await getRegistration();
|
||||
const sub = await reg?.pushManager.getSubscription();
|
||||
setSubscribed(Boolean(sub));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const enable = useCallback(async (): Promise<boolean> => {
|
||||
if (!supported || !configured || !VAPID_PUBLIC_KEY) return false;
|
||||
setBusy(true);
|
||||
try {
|
||||
const perm = await Notification.requestPermission();
|
||||
setPermission(perm as PushPermission);
|
||||
if (perm !== "granted") return false;
|
||||
|
||||
const reg = await getRegistration();
|
||||
if (!reg) return false;
|
||||
|
||||
let sub = await reg.pushManager.getSubscription();
|
||||
if (!sub) {
|
||||
sub = await reg.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToBuffer(VAPID_PUBLIC_KEY),
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch("/api/push/subscribe", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ subscription: sub.toJSON() }),
|
||||
});
|
||||
if (!res.ok) return false;
|
||||
|
||||
setSubscribed(true);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, [supported, configured]);
|
||||
|
||||
const disable = useCallback(async (): Promise<boolean> => {
|
||||
if (!supported) return false;
|
||||
setBusy(true);
|
||||
try {
|
||||
const reg = await getRegistration();
|
||||
const sub = await reg?.pushManager.getSubscription();
|
||||
if (sub) {
|
||||
await fetch("/api/push/unsubscribe", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ endpoint: sub.endpoint }),
|
||||
}).catch(() => {});
|
||||
await sub.unsubscribe().catch(() => {});
|
||||
}
|
||||
setSubscribed(false);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, [supported]);
|
||||
|
||||
const sendTest = useCallback(async (): Promise<boolean> => {
|
||||
setBusy(true);
|
||||
try {
|
||||
const res = await fetch("/api/push/test", { method: "POST" });
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
supported,
|
||||
configured,
|
||||
permission,
|
||||
subscribed,
|
||||
busy,
|
||||
enable,
|
||||
disable,
|
||||
sendTest,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user