/** * Captura de `beforeinstallprompt` a nivel de módulo, no dentro de un hook. * * El navegador dispara `beforeinstallprompt` **una sola vez** por carga de página, poco * después del `load`, y no lo repite: no hay forma de volver a pedirlo. Un listener que * se registra al montar un componente sencillamente se lo pierde si el componente aparece * más tarde. * * Y aparece más tarde: `LoginPage` se carga con `React.lazy`, así que `InstallAppPrompt` * monta después de una ida y vuelta de red extra para traer su chunk. En local eso es un * milisegundo y no se nota; sobre una conexión real el evento ya pasó y el botón de * instalar no aparecía nunca. `pwa-e2e.mjs` contra el dominio HTTPS lo detecta; contra * `localhost` no, porque ahí el chunk llega antes que el evento. * * Por eso el listener se registra al evaluar este módulo, que `main.tsx` importa antes de * montar React, y el evento queda guardado hasta que alguien lo pida. */ export interface BeforeInstallPromptEvent extends Event { prompt: () => Promise; userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>; } let capturado: BeforeInstallPromptEvent | null = null; let instalado = false; const suscriptores = new Set<() => void>(); function avisar() { for (const notificar of suscriptores) notificar(); } if (typeof window !== "undefined") { window.addEventListener("beforeinstallprompt", (event) => { // `preventDefault` evita el mini-infobar de Chrome; el botón propio queda a cargo. event.preventDefault(); capturado = event as BeforeInstallPromptEvent; avisar(); }); window.addEventListener("appinstalled", () => { capturado = null; instalado = true; avisar(); }); } /** El evento pendiente, o null si el navegador nunca lo disparó. */ export function eventoDisponible(): BeforeInstallPromptEvent | null { return capturado; } export function yaInstalado(): boolean { return instalado; } /** Lo saca del almacén: un `BeforeInstallPromptEvent` solo se puede usar una vez. */ export function tomarEvento(): BeforeInstallPromptEvent | null { const evento = capturado; if (evento) { capturado = null; avisar(); } return evento; } /** Lo devuelve si `prompt()` falló, para no perder la única oportunidad de instalar. */ export function devolverEvento(evento: BeforeInstallPromptEvent) { capturado = evento; avisar(); } export function marcarInstalado() { capturado = null; instalado = true; avisar(); } export function suscribir(notificar: () => void): () => void { suscriptores.add(notificar); return () => { suscriptores.delete(notificar); }; }