// GCG export delivery: share on mobile (Web Share API with a file) where supported, // otherwise download via a Blob + 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; /** 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 { 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 // 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); } type TextShareNav = Pick & { canShare?: Navigator['canShare'] }; /** * pickTextShare decides how to deliver a plain-text payload: through the OS share sheet (Web Share, * available on mobile including the Telegram Mini App) or, on a desktop browser without it, a * clipboard copy. Pure, so it is unit-tested with a mock navigator. */ export function pickTextShare(nav: TextShareNav | undefined): 'share' | 'copy' { if (nav && typeof nav.share === 'function' && (typeof nav.canShare !== 'function' || nav.canShare({ text: 'x' }))) { return 'share'; } return 'copy'; } /** * shareText delivers text through the OS share sheet where supported, else copies it to the * clipboard. It reports the path taken — 'shared', 'copied', or 'failed' (a cancelled share or an * unavailable clipboard) — so the caller can confirm a silent copy to the user. Like * shareOrDownloadGcg it never strands the webview: a cancelled share simply does nothing. */ export async function shareText(text: string, title: string): Promise<'shared' | 'copied' | 'failed'> { const nav = typeof navigator !== 'undefined' ? navigator : undefined; if (!nav) return 'failed'; if (pickTextShare(nav) === 'share') { try { await nav.share({ title, text }); return 'shared'; } catch { return 'failed'; } } try { await nav.clipboard.writeText(text); return 'copied'; } catch { return 'failed'; } }