"use client"; import { useEffect } from "react"; import type { ReactNode } from "react"; import { useTranslations } from "next-intl"; import { usePathname, useRouter } from "@/i18n/navigation"; import { getPathPrefix } from "@/lib/browser-navigation"; import { parseMailto } from "@/lib/protocol-handlers/mailto"; import { parseWebcal } from "@/lib/protocol-handlers/webcal"; import { listenForMailtoRequests, notifyPendingMailto, notifyPendingWebcal, requestOpenMailtoInExistingClient, savePendingMailto, savePendingWebcal, } from "@/lib/protocol-handlers/session"; import { useSettingsStore } from "@/stores/settings-store"; type LaunchParams = { targetURL?: string }; type StandaloneNavigator = Navigator & { standalone?: boolean }; declare global { interface Window { launchQueue?: { setConsumer: (consumer: (launchParams: LaunchParams) => void) => void; }; } } function getProtocolLaunch(targetURL: string): | { kind: "mailto"; raw: string } | { kind: "webcal"; raw: string } | null { let url: URL; try { url = new URL(targetURL, window.location.origin); } catch { return null; } if (url.origin !== window.location.origin) return null; const raw = url.searchParams.get("url"); if (!raw) return null; if (url.pathname.includes("/protocol/mailto")) return { kind: "mailto", raw }; if (url.pathname.includes("/protocol/webcal")) return { kind: "webcal", raw }; return null; } function isStandaloneDisplayMode() { return window.matchMedia?.("(display-mode: standalone)").matches || (navigator as StandaloneNavigator).standalone === true; } function openProtocolInNewTab(protocol: "mailto" | "webcal", raw: string): boolean { const url = `${getPathPrefix()}/protocol/${protocol}?url=${encodeURIComponent(raw)}&fallback=1`; const opened = window.open(url, "_blank"); if (!opened) return false; opened.opener = null; return true; } interface ProtocolLaunchHandlerProviderProps { children: ReactNode; } export function ProtocolLaunchHandlerProvider({ children }: ProtocolLaunchHandlerProviderProps) { const t = useTranslations("protocol_handlers"); const router = useRouter(); const pathname = usePathname(); useEffect(() => { if (pathname.startsWith("/protocol/")) return; return listenForMailtoRequests((pending) => { savePendingMailto(pending); notifyPendingMailto(); if (pathname !== "/") router.push("/"); }, () => ({ path: pathname, standalone: isStandaloneDisplayMode(), focusNotificationTitle: t("focus_notification_title"), focusNotificationBody: t("focus_notification_body"), })); }, [pathname, router, t]); useEffect(() => { if (typeof window === "undefined" || !window.launchQueue) return; window.launchQueue.setConsumer((launchParams) => { if (!launchParams.targetURL) return; const launch = getProtocolLaunch(launchParams.targetURL); if (!launch) return; if (launch.kind === "mailto") { const parsed = parseMailto(launch.raw); if (!parsed) return; if (useSettingsStore.getState().protocolOpenMode === "new-tab") { if (openProtocolInNewTab("mailto", launch.raw)) return; savePendingMailto(parsed); notifyPendingMailto(); if (pathname !== "/") router.push("/"); return; } void requestOpenMailtoInExistingClient(parsed).then((delivered) => { if (delivered) return; savePendingMailto(parsed); notifyPendingMailto(); if (pathname !== "/") router.push("/"); }); return; } const parsed = parseWebcal(launch.raw); if (!parsed) return; if (useSettingsStore.getState().protocolOpenMode === "new-tab") { if (openProtocolInNewTab("webcal", launch.raw)) return; } savePendingWebcal(parsed); notifyPendingWebcal(); if (pathname !== "/calendar") router.push("/calendar"); }); }, [pathname, router]); return children; }