"use client"; import { useEffect, useState } from "react"; type BeforeInstallPromptEvent = Event & { prompt: () => Promise; userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string; }>; }; type NotificationState = | "checking" | "idle" | "pending" | "enabled" | "blocked" | "unsupported"; 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 index = 0; index < rawData.length; index += 1) { outputArray[index] = rawData.charCodeAt(index); } return outputArray; } export function PwaControls() { const vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || ""; const [installPromptEvent, setInstallPromptEvent] = useState(null); const [installing, setInstalling] = useState(false); const [isStandalone, setIsStandalone] = useState(false); const [notificationState, setNotificationState] = useState("checking"); const [notice, setNotice] = useState(null); useEffect(() => { let active = true; function handleBeforeInstallPrompt(event: Event) { event.preventDefault(); if (!active) { return; } setInstallPromptEvent(event as BeforeInstallPromptEvent); } async function syncExistingSubscription(subscription: PushSubscription) { await fetch("/api/push/subscribe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ subscription: subscription.toJSON(), }), }); } async function bootstrap() { const standalone = window.matchMedia("(display-mode: standalone)").matches || Boolean((navigator as Navigator & { standalone?: boolean }).standalone); setIsStandalone(standalone); if ( !("serviceWorker" in navigator) || !("PushManager" in window) || typeof Notification === "undefined" ) { setNotificationState("unsupported"); return; } try { await navigator.serviceWorker.register("/sw.js"); const registration = await navigator.serviceWorker.ready; const existingSubscription = await registration.pushManager.getSubscription(); if (!active) { return; } if (existingSubscription) { if (vapidPublicKey) { await syncExistingSubscription(existingSubscription); } setNotificationState("enabled"); return; } setNotificationState( Notification.permission === "denied" ? "blocked" : "idle", ); } catch { if (active) { setNotificationState("unsupported"); } } } window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt); void bootstrap(); return () => { active = false; window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt); }; }, [vapidPublicKey]); async function enableNotifications() { if (!vapidPublicKey) { setNotice("当前还没有配置推送服务,先将应用安装到桌面即可。\n"); return; } setNotificationState("pending"); setNotice(null); try { const permission = await Notification.requestPermission(); if (permission !== "granted") { setNotificationState(permission === "denied" ? "blocked" : "idle"); setNotice( permission === "denied" ? "浏览器已经拒绝消息提醒,请在系统设置里重新打开通知权限。\n" : "这次没有打开通知权限,稍后仍可再次开启。\n", ); return; } const registration = await navigator.serviceWorker.ready; let subscription = await registration.pushManager.getSubscription(); if (!subscription) { subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey), }); } await fetch("/api/push/subscribe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ subscription: subscription.toJSON(), }), }); setNotificationState("enabled"); setNotice("已开启消息提醒,长辈的新留言会直接推送到您的设备。\n"); } catch { setNotificationState("idle"); setNotice("打开消息提醒失败,请稍后再试。\n"); } } async function disableNotifications() { try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await fetch("/api/push/unsubscribe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ endpoint: subscription.endpoint, }), }); await subscription.unsubscribe(); } setNotificationState("idle"); setNotice("这台设备的消息提醒已关闭。\n"); } catch { setNotice("关闭消息提醒失败,请稍后再试。\n"); } } async function installApp() { if (!installPromptEvent) { setNotice("如果浏览器没有弹出安装按钮,可以从菜单里选择“安装应用”或“添加到主屏幕”。\n"); return; } setInstalling(true); setNotice(null); try { await installPromptEvent.prompt(); const choice = await installPromptEvent.userChoice; if (choice.outcome === "accepted") { setInstallPromptEvent(null); setNotice("安装提示已经确认,完成后就能像应用一样直接打开。\n"); } else { setNotice("这次先关闭了安装提示,之后仍可从浏览器菜单安装。\n"); } } finally { setInstalling(false); } } return (

快捷设置

安装到桌面,随时收到长辈的消息

安装后打开更快,开启提醒后,长辈每次留言都能立刻收到通知。

{isStandalone ? "已安装到桌面" : "当前在浏览器中打开"} {notificationState === "enabled" ? "消息提醒已开启" : notificationState === "blocked" ? "通知权限已被拒绝" : "消息提醒未开启"}
{notificationState === "enabled" ? ( ) : ( )}
{notice ? (

{notice}

) : null}
); }