Files
cozsweet-frontend-nextjs/src/app/chat/components/pwa-install-overlay.tsx
T

105 lines
2.7 KiB
TypeScript

"use client";
/**
* PwaInstallOverlay PWA 安装提示触发器
*
* 行为:
* - 调用方达到消息阈值后启用检查
* - 仅在浏览器可安装、尚未安装且未展示过时继续
* - 延迟 1.5 秒显示 PwaInstallDialog
* - 用户确认后调用浏览器安装提示
*/
import { useEffect } from "react";
import {
canShowPwaInstallPromptOnce,
recordPwaInstallPromptShown,
} from "@/lib/chat/pwa_install_prompt";
import { pwaUtil } from "@/utils/pwa";
import { PwaInstallDialog } from "./pwa-install-dialog";
const DIALOG_DELAY_MS = 1500;
export interface PwaInstallOverlayProps {
enabled: boolean;
}
export function PwaInstallOverlay({ enabled }: PwaInstallOverlayProps) {
useEffect(() => {
if (!enabled) return;
let mounted = true;
let timer: number | undefined;
let unsubscribe: (() => void) | undefined;
const init = async () => {
// 提前挂 beforeinstallprompt 监听,避免用户点 OK 时事件已在更早时机丢失。
pwaUtil.prepareInstallPrompt();
const tryScheduleDialog = async () => {
const shouldShowDialog = await shouldShowPwaInstallDialog();
if (!mounted || !shouldShowDialog || timer !== undefined) return;
// 达到聊天数量阈值后稍作停顿,避免弹窗与消息刷新挤在同一瞬间。
timer = window.setTimeout(() => {
if (!mounted) return;
showPwaDialog();
}, DIALOG_DELAY_MS);
};
await tryScheduleDialog();
unsubscribe = pwaUtil.subscribe(() => {
void tryScheduleDialog();
});
};
void init();
return () => {
mounted = false;
unsubscribe?.();
if (timer !== undefined) window.clearTimeout(timer);
};
}, [enabled]);
return null;
}
async function shouldShowPwaInstallDialog() {
if (!pwaUtil.canShowInstallEntry()) return false;
return canShowPwaInstallPromptOnce();
}
function showPwaDialog() {
if (typeof document === "undefined") return;
// Persist before rendering so remounts cannot schedule a second dialog.
void recordPwaInstallPromptShown();
const root = document.createElement("div");
document.body.appendChild(root);
import("react-dom/client").then(({ createRoot }) => {
const reactRoot = createRoot(root);
reactRoot.render(<PwaInstallDialogMount rootEl={root} reactRoot={reactRoot} />);
});
}
function PwaInstallDialogMount({
rootEl,
reactRoot,
}: {
rootEl: HTMLElement;
reactRoot: { unmount: () => void };
}) {
return (
<PwaInstallDialog
onClose={() => {
reactRoot.unmount();
rootEl.remove();
}}
onInstall={async () => {
// The utility owns deferred-prompt lookup and browser prompting.
await pwaUtil.install();
}}
/>
);
}