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.
		
		
		
		
		
			
		
			
				
					
					
						
							181 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							181 lines
						
					
					
						
							6.3 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'; | |
| 
 | |
| // Use Electron's built-in single instance lock | |
| const gotTheLock = app.requestSingleInstanceLock(); | |
| 
 | |
| if (!gotTheLock) { | |
|   console.log('[Electron] Another instance is already running. Exiting 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: 'editMenu' }, | |
|   { 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 | |
| // DISABLED: Auto-updates not supported on Gitea hosting | |
| // 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 and show dialog) | |
|   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(); | |
|        | |
|       // Show user-friendly dialog to inform about single instance | |
|       dialog.showMessageBox(mainWindow, { | |
|         type: 'info', | |
|         title: 'TimeSafari Already Running', | |
|         message: 'TimeSafari is already running in another window.', | |
|         detail: 'Please use this existing TimeSafari window instead of opening a new one.', | |
|         buttons: ['OK'] | |
|       }).catch((error) => { | |
|         console.error('[Electron] Error showing dialog:', error); | |
|       }); | |
|     } | |
|   }); | |
|    | |
|   // Auto-updates disabled - not supported on Gitea hosting | |
|   // 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'  | |
|     }; | |
|   } | |
| });
 | |
| 
 |