Skip to content

Commit f32622d

Browse files
IM.codesclaude
andcommitted
fix: iOS file download — use Capacitor Filesystem + Share sheet
WKWebView ignores <a download> attribute, so file downloads silently fail on iOS. On native platforms, save to cache via Filesystem plugin then open the share sheet so the user can save/share the file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f33faf4 commit f32622d

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

web/package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
"@capacitor/app": "^8.0.1",
1818
"@capacitor/browser": "^8.0.2",
1919
"@capacitor/core": "^8.2.0",
20+
"@capacitor/filesystem": "^8.1.2",
2021
"@capacitor/ios": "^8.2.0",
2122
"@capacitor/preferences": "^8.0.1",
2223
"@capacitor/push-notifications": "^8.0.2",
24+
"@capacitor/share": "^8.0.1",
2325
"@capacitor/splash-screen": "^8.0.1",
2426
"@capacitor/status-bar": "^8.0.1",
2527
"@capgo/capacitor-speech-recognition": "^8.0.10",

web/src/api.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -927,15 +927,45 @@ export async function downloadAttachment(serverId: string, attachmentId: string)
927927
if (plainMatch) filename = plainMatch[1];
928928
}
929929
}
930-
// Trigger browser download
931-
const url = URL.createObjectURL(blob);
932-
const a = document.createElement('a');
933-
a.href = url;
934-
a.download = filename;
935-
document.body.appendChild(a);
936-
a.click();
937-
document.body.removeChild(a);
938-
URL.revokeObjectURL(url);
930+
// Trigger download — WKWebView (iOS Capacitor) ignores <a download>,
931+
// so on native platforms we use Capacitor Filesystem + share sheet.
932+
const isNative = !!(globalThis as Record<string, unknown>).Capacitor;
933+
if (isNative) {
934+
try {
935+
const { Filesystem, Directory } = await import('@capacitor/filesystem');
936+
const reader = new FileReader();
937+
const base64 = await new Promise<string>((resolve, reject) => {
938+
reader.onload = () => {
939+
const result = reader.result as string;
940+
resolve(result.split(',')[1] ?? result);
941+
};
942+
reader.onerror = reject;
943+
reader.readAsDataURL(blob);
944+
});
945+
const saved = await Filesystem.writeFile({
946+
path: filename,
947+
data: base64,
948+
directory: Directory.Cache,
949+
});
950+
// Open share sheet so user can save/share the file
951+
const { Share } = await import('@capacitor/share');
952+
await Share.share({ url: saved.uri, title: filename });
953+
} catch {
954+
// Fallback: open blob URL in new tab (may prompt Safari download)
955+
const url = URL.createObjectURL(blob);
956+
window.open(url, '_blank');
957+
setTimeout(() => URL.revokeObjectURL(url), 60_000);
958+
}
959+
} else {
960+
const url = URL.createObjectURL(blob);
961+
const a = document.createElement('a');
962+
a.href = url;
963+
a.download = filename;
964+
document.body.appendChild(a);
965+
a.click();
966+
document.body.removeChild(a);
967+
URL.revokeObjectURL(url);
968+
}
939969
}
940970

941971
export async function previewAttachment(serverId: string, attachmentId: string): Promise<void> {

0 commit comments

Comments
 (0)