e850ecd83b
On iOS WKWebView (the Telegram Mini App), cancelling the Web Share sheet fell through to the Blob <a download> fallback. iOS ignores the download attribute, so clicking the anchor navigated the webview to the blob: URL — replacing the SPA with the raw GCG file, with no way back (force-quit only). The share path no longer falls back to a download: Web Share is available on that platform, so a cancelled or failed share is a no-op and the user can retry. The Blob download stays the desktop-only path (no Web Share).
54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
// GCG export delivery: share on mobile (Web Share API with a file) where supported,
|
|
// otherwise download via a Blob + <a download> on desktop. The Capacitor-native file
|
|
// save lands with the native wrapper; the Web Share path already covers mobile
|
|
// browsers. pickGcgDelivery is the pure decision, unit-tested with a mock navigator.
|
|
|
|
import type { GcgExport } from './model';
|
|
|
|
type ShareNav = Pick<Navigator, 'canShare' | 'share'>;
|
|
|
|
/** pickGcgDelivery decides between the Web Share API and a Blob download for a file. */
|
|
export function pickGcgDelivery(nav: ShareNav | undefined, file: File): 'share' | 'download' {
|
|
if (
|
|
nav &&
|
|
typeof nav.canShare === 'function' &&
|
|
typeof nav.share === 'function' &&
|
|
nav.canShare({ files: [file] })
|
|
) {
|
|
return 'share';
|
|
}
|
|
return 'download';
|
|
}
|
|
|
|
/** shareOrDownloadGcg shares the GCG file where supported, else triggers a download. */
|
|
export async function shareOrDownloadGcg(gcg: GcgExport): Promise<void> {
|
|
const file = new File([gcg.content], gcg.filename, { type: 'application/x-gcg' });
|
|
const nav = typeof navigator !== 'undefined' ? navigator : undefined;
|
|
if (pickGcgDelivery(nav, file) === 'share' && nav) {
|
|
// Web Share is available (mobile, including the iOS Telegram Mini App): use it and stop here
|
|
// whatever the outcome. Do NOT fall back to the Blob download — on iOS WKWebView an
|
|
// <a download> navigates the webview to the blob: URL, replacing the SPA with the raw file
|
|
// and stranding the app, so a cancelled or failed share must simply do nothing (the user can
|
|
// retry). The download path is only for desktop browsers without Web Share.
|
|
try {
|
|
await nav.share({ files: [file], title: gcg.filename });
|
|
} catch {
|
|
/* cancelled or failed — intentionally a no-op (see above) */
|
|
}
|
|
return;
|
|
}
|
|
downloadFile(gcg.content, gcg.filename);
|
|
}
|
|
|
|
function downloadFile(content: string, filename: string): void {
|
|
if (typeof document === 'undefined') return;
|
|
const url = URL.createObjectURL(new Blob([content], { type: 'application/x-gcg' }));
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
URL.revokeObjectURL(url);
|
|
}
|