mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
The installed app only handled mailto: and webcal: launches. The launch consumer dropped files, and nothing could hand a File to the composer or the import dialogs, because the pending mailto is JSON in sessionStorage. - manifest: `shortcuts` named in the browser's language, `file_handlers` for .eml, .ics and .vcf, and `share_target`, all with the base path and per-domain branding. Lite's manifest is unchanged. - `lib/launch-handoff.ts`: the one place files and links wait, in memory and in the Cache API, so they survive a sign-in that leaves the page. The desktop bridge can call the same functions, since launchQueue never fires in Electron. Sign-out clears it. - launch consumer: routes files by type, and takes a shortcut to an open window, which focus-existing would otherwise only focus. - .eml opens in a new host for the preview, also before sign-in, with the strictest device policy deciding about saving. .ics and .vcf open their import dialogs through a new `initialFile` prop. A .ics needs the server, so offline the dialog says so and carries on once back online. - composer: `initialFiles` goes through `addFiles`, so plugin vetoes and size limits still apply, also from a Pro compose tab. - `sw.js` takes the share POST before the offline check and keeps the files for the page. Without a worker, `/protocol/share` passes text and links on and the page says the files are lost. `protocol` joins the app sections and the handler pages join the shell, so a mailto: launch offline no longer gets a network error. Browsers without launchQueue or a service worker behave as before.
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState, type ComponentType, type ComponentProps } from "react";
|
|
import { useTranslations } from "next-intl";
|
|
import type { FilePreviewModal as FilePreviewModalType } from "@/components/files/file-preview-modal";
|
|
import { useHandoffFiles } from "@/hooks/use-handoff-files";
|
|
import { saveBlobAs } from "@/lib/download-guard";
|
|
import { toast } from "@/stores/toast-store";
|
|
|
|
type PreviewComponent = ComponentType<ComponentProps<typeof FilePreviewModalType>>;
|
|
|
|
/**
|
|
* Shows a .eml opened from the OS. It parses in the browser, so it works
|
|
* offline and before sign-in. `policyAccount` stays out: the strictest device
|
|
* policy of the accounts signed in here decides about saving (FilePreviewModal
|
|
* and saveBlobAs check it).
|
|
*
|
|
* The preview is loaded when a file arrives, to keep it off the layout's
|
|
* bundle, and by hand rather than with next/dynamic: without a connection and
|
|
* without the offline shell its chunk can't load, which has to end in a
|
|
* message, not in the error page, and work again later.
|
|
*/
|
|
export function LaunchFileHost() {
|
|
const t = useTranslations("pwaLaunch");
|
|
const { file, done } = useHandoffFiles("eml", true);
|
|
const [Preview, setPreview] = useState<PreviewComponent | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!file || Preview) return;
|
|
let stale = false;
|
|
import("@/components/files/file-preview-modal")
|
|
.then((module) => {
|
|
if (!stale) setPreview(() => module.FilePreviewModal);
|
|
})
|
|
.catch(() => {
|
|
if (stale) return;
|
|
toast.error(t("open_failed"));
|
|
done();
|
|
});
|
|
return () => { stale = true; };
|
|
}, [file, Preview, done, t]);
|
|
|
|
const getFileContent = useCallback(async () => {
|
|
if (!file) throw new Error("no file");
|
|
return { blob: file, contentType: "message/rfc822" };
|
|
}, [file]);
|
|
|
|
const onDownload = useCallback(() => {
|
|
if (file) saveBlobAs(file, file.name, { kind: "attachment" });
|
|
}, [file]);
|
|
|
|
if (!file || !Preview) return null;
|
|
return (
|
|
<Preview
|
|
key={`${file.name}:${file.lastModified}`}
|
|
name={file.name}
|
|
onClose={done}
|
|
onDownload={onDownload}
|
|
getFileContent={getFileContent}
|
|
/>
|
|
);
|
|
}
|