feat: add download file method for files generated by plugin

This commit is contained in:
Paulhenry Saux
2026-07-13 21:18:36 +02:00
committed by Linus Rath
parent a08a9e9ed3
commit a679d82cc3
4 changed files with 28 additions and 1 deletions
+23
View File
@@ -60,6 +60,7 @@ const PERM_PER_METHOD: Record<string, Permission | null> = {
'ui.prompt': null,
'ui.rerenderEmail': null,
'ui.openExternalUrl': null,
'ui.downloadFile': 'ui:download-file'
};
function hasPermission(plugin: InstalledPlugin, perm: Permission): boolean {
@@ -400,6 +401,24 @@ async function saveFile(formerFileID:string, file: File): Promise<string> {
return fileId;
}
// ─── Download files generated by the plugin. This is not user's files or attachments ──────────────────────────
async function downloadFile(args: { content: string; filename: string; contentType?: string }): Promise<void> {
const { content, filename, contentType = 'application/json' } = args;
try {
const url = URL.createObjectURL(new Blob([content], { type: contentType }));
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
throw new Error(`Failed to download file: ${error}`);
}
}
// ─── admin config (same as before) ────────────────────────────
async function adminGetAll(pluginId: string): Promise<Record<string, unknown>> {
@@ -547,6 +566,10 @@ export async function dispatchApiCall(
window.open(parsed.toString(), '_blank', 'noopener,noreferrer');
return undefined;
}
case 'ui.downloadFile': {
const opts = args[0] as { content: string; filename: string; contentType?: string };
return downloadFile(opts);
}
default:
throw new Error(`Unhandled method "${method}"`);
+1 -1
View File
@@ -245,7 +245,7 @@ export const API_METHODS = [
'jmap.fetchBlob', 'jmap.sendRaw',
'admin.getConfig', 'admin.getAllConfig', 'admin.setConfig', 'admin.deleteConfig',
'toast.success', 'toast.error', 'toast.info', 'toast.warning',
'ui.confirm', 'ui.alert', 'ui.prompt', 'ui.rerenderEmail', 'ui.openExternalUrl',
'ui.confirm', 'ui.alert', 'ui.prompt', 'ui.rerenderEmail', 'ui.openExternalUrl', 'ui.downloadFile'
] as const;
export type ApiMethod = (typeof API_METHODS)[number];
+3
View File
@@ -232,6 +232,9 @@ function buildPluginApi(manifest: PluginManifest) {
/** Opens an http/https URL in a new tab via host `window.open`. */
openExternalUrl: (url: string, target?: string) =>
callApi('ui.openExternalUrl', [url, target]) as Promise<void>,
/** Downloads a file generated by the plugin. Not a user's file or attachment. */
downloadFile: (opts: { content: string; filename: string; contentType?: string }) =>
callApi('ui.downloadFile', [opts]) as Promise<void>,
},
admin: {
getConfig: (key: string) => callApi('admin.getConfig', [key]),
+1
View File
@@ -925,6 +925,7 @@ export const ALL_PERMISSIONS = [
'http:post', 'http:fetch',
'ui:observe', 'ui:toolbar', 'ui:app-top-banner', 'ui:email-banner', 'ui:email-footer',
'ui:email-details',
'ui:download-file',
'ui:composer-toolbar', 'ui:composer-sidebar',
'ui:sidebar-widget', 'ui:settings-section',
'ui:context-menu', 'ui:navigation-rail', 'ui:keyboard',