105 lines
2.7 KiB
TypeScript
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();
|
|
}}
|
|
/>
|
|
);
|
|
}
|