feat: implement secure IPC-based file export for Electron

Replace sandboxed Capacitor filesystem with native IPC for reliable file exports:
- Add IPC handler in main process for direct Downloads folder access
- Expose secure electronAPI via contextBridge in preload script
- Update ElectronPlatformService to use native IPC with web fallback
- Add TypeScript definitions for electron APIs
- Fix file export issues where files were trapped in virtual filesystem
- Enable proper date-stamped backup filenames in Downloads folder
- Follow Electron security best practices with process isolation

Files now export directly to ~/Downloads with exact path feedback.
This commit is contained in:
Matthew Raymer
2025-07-06 03:46:28 +00:00
parent 10562b7c47
commit e883029531
7 changed files with 328 additions and 159 deletions

View File

@@ -1,10 +1,12 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron';
import type { MenuItemConstructorOptions } from 'electron';
import { app, MenuItem } from 'electron';
import { app, MenuItem, ipcMain } from 'electron';
import electronIsDev from 'electron-is-dev';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
import { promises as fs } from 'fs';
import { join } from 'path';
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
@@ -106,3 +108,38 @@ app.on('activate', async function () {
});
// Place all ipc or other electron api calls and custom functionality under this line
/**
* IPC Handler for exporting data to the user's Downloads folder.
*
* This provides a secure, native way to save files directly to the Downloads
* directory using the main process's file system access.
*
* @param fileName - The name of the file to save (including extension)
* @param data - The data to write to the file (string or buffer)
* @returns Promise<{success: boolean, path?: string, error?: string}>
*/
ipcMain.handle('export-data-to-downloads', async (_event, fileName: string, data: string) => {
try {
// Get the user's Downloads directory path
const downloadsDir = app.getPath('downloads');
const filePath = join(downloadsDir, fileName);
// Write the file to the Downloads directory
await fs.writeFile(filePath, data, 'utf-8');
console.log(`[Electron Main] File exported successfully: ${filePath}`);
return {
success: true,
path: filePath
};
} catch (error) {
console.error(`[Electron Main] File export failed:`, error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
});

View File

@@ -1,4 +1,34 @@
import { contextBridge, ipcRenderer } from 'electron';
require('./rt/electron-rt');
//////////////////////////////
// User Defined Preload scripts below
console.log('User Preload!');
/**
* Expose secure IPC APIs to the renderer process.
*
* This creates a bridge between the sandboxed renderer and the main process,
* allowing secure file operations while maintaining Electron's security model.
*/
contextBridge.exposeInMainWorld('electronAPI', {
/**
* Export data to the user's Downloads folder.
*
* @param fileName - The name of the file to save (e.g., 'backup-2025-07-06.json')
* @param data - The content to write to the file (string)
* @returns Promise<{success: boolean, path?: string, error?: string}>
*
* @example
* ```typescript
* const result = await window.electronAPI.exportData('my-backup.json', JSON.stringify(data));
* if (result.success) {
* console.log('File saved to:', result.path);
* } else {
* console.error('Export failed:', result.error);
* }
* ```
*/
exportData: (fileName: string, data: string) =>
ipcRenderer.invoke('export-data-to-downloads', fileName, data)
});