You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
6.0 KiB
180 lines
6.0 KiB
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
|
|
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron';
|
|
import type { MenuItemConstructorOptions } from 'electron';
|
|
import { app, MenuItem, ipcMain, dialog } 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';
|
|
|
|
// Single instance enforcement
|
|
const gotTheLock = app.requestSingleInstanceLock();
|
|
|
|
if (!gotTheLock) {
|
|
console.log('[Electron] Another instance is already running. Exiting...');
|
|
|
|
// Show user-friendly dialog before quitting
|
|
dialog.showMessageBox({
|
|
type: 'info',
|
|
title: 'TimeSafari Already Running',
|
|
message: 'TimeSafari is already running in another window.',
|
|
detail: 'Please use the existing TimeSafari window instead of opening a new one.',
|
|
buttons: ['OK']
|
|
}).then(() => {
|
|
app.quit();
|
|
});
|
|
|
|
// Exit immediately
|
|
process.exit(0);
|
|
}
|
|
|
|
// Graceful handling of unhandled errors.
|
|
unhandled({
|
|
logger: (error) => {
|
|
// Suppress EPIPE errors which are common in AppImages due to console output issues
|
|
if (error.message && error.message.includes('EPIPE')) {
|
|
return; // Don't log EPIPE errors
|
|
}
|
|
console.error('Unhandled error:', error);
|
|
}
|
|
});
|
|
|
|
// Handle EPIPE errors on stdout/stderr to prevent crashes
|
|
process.stdout.on('error', (err) => {
|
|
if (err.code === 'EPIPE') {
|
|
// Ignore EPIPE errors on stdout
|
|
return;
|
|
}
|
|
console.error('stdout error:', err);
|
|
});
|
|
|
|
process.stderr.on('error', (err) => {
|
|
if (err.code === 'EPIPE') {
|
|
// Ignore EPIPE errors on stderr
|
|
return;
|
|
}
|
|
console.error('stderr error:', err);
|
|
});
|
|
|
|
// Define our menu templates (these are optional)
|
|
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })];
|
|
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
|
|
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
|
{ role: 'viewMenu' },
|
|
];
|
|
|
|
// Get Config options from capacitor.config
|
|
const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig();
|
|
|
|
// Initialize our app. You can pass menu templates into the app here.
|
|
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
|
|
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
|
|
|
|
// If deeplinking is enabled then we will set it up here.
|
|
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
|
|
setupElectronDeepLinking(myCapacitorApp, {
|
|
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp',
|
|
});
|
|
}
|
|
|
|
// If we are in Dev mode, use the file watcher components.
|
|
if (electronIsDev) {
|
|
setupReloadWatcher(myCapacitorApp);
|
|
}
|
|
|
|
// Configure auto-updater
|
|
autoUpdater.on('error', (error) => {
|
|
console.log('Auto-updater error (suppressed):', error.message);
|
|
// Don't show error dialogs for update check failures
|
|
});
|
|
|
|
// Run Application
|
|
(async () => {
|
|
// Wait for electron app to be ready.
|
|
await app.whenReady();
|
|
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
|
|
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
|
|
// Initialize our app, build windows, and load content.
|
|
await myCapacitorApp.init();
|
|
|
|
// Handle second instance launch (focus existing window)
|
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
|
console.log('[Electron] Second instance attempted to launch');
|
|
|
|
// Someone tried to run a second instance, we should focus our window instead
|
|
const mainWindow = myCapacitorApp.getMainWindow();
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) {
|
|
mainWindow.restore();
|
|
}
|
|
mainWindow.focus();
|
|
}
|
|
});
|
|
|
|
// Only check for updates in production builds, not in development or AppImage
|
|
if (!electronIsDev && !process.env.APPIMAGE) {
|
|
try {
|
|
autoUpdater.checkForUpdatesAndNotify();
|
|
} catch (error) {
|
|
console.log('Update check failed (suppressed):', error);
|
|
}
|
|
}
|
|
})();
|
|
|
|
// Handle when all of our windows are close (platforms have their own expectations).
|
|
app.on('window-all-closed', function () {
|
|
// On OS X it is common for applications and their menu bar
|
|
// to stay active until the user quits explicitly with Cmd + Q
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
// When the dock icon is clicked.
|
|
app.on('activate', async function () {
|
|
// On OS X it's common to re-create a window in the app when the
|
|
// dock icon is clicked and there are no other windows open.
|
|
if (myCapacitorApp.getMainWindow().isDestroyed()) {
|
|
await myCapacitorApp.init();
|
|
}
|
|
});
|
|
|
|
// 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'
|
|
};
|
|
}
|
|
});
|
|
|