import type { CapacitorElectronConfig } from '@capacitor-community/electron'; import { CapElectronEventEmitter, CapacitorSplashScreen, setupCapacitorElectronPlugins, } from '@capacitor-community/electron'; import chokidar from 'chokidar'; import type { MenuItemConstructorOptions } from 'electron'; import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron'; //import electronIsDev from 'electron-is-dev'; var electronIsDev = false; import electronServe from 'electron-serve'; import windowStateKeeper from 'electron-window-state'; import { join } from 'path'; // Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode. const reloadWatcher = { debouncer: null, ready: false, watcher: null, }; export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void { reloadWatcher.watcher = chokidar .watch(join(app.getAppPath(), 'app'), { ignored: /[/\\]\./, persistent: true, }) .on('ready', () => { reloadWatcher.ready = true; }) .on('all', (_event, _path) => { if (reloadWatcher.ready) { clearTimeout(reloadWatcher.debouncer); reloadWatcher.debouncer = setTimeout(async () => { electronCapacitorApp.getMainWindow().webContents.reload(); reloadWatcher.ready = false; clearTimeout(reloadWatcher.debouncer); reloadWatcher.debouncer = null; reloadWatcher.watcher = null; setupReloadWatcher(electronCapacitorApp); }, 1500); } }); } // Define our class to manage our app. export class ElectronCapacitorApp { private MainWindow: BrowserWindow | null = null; private SplashScreen: CapacitorSplashScreen | null = null; private TrayIcon: Tray | null = null; private CapacitorFileConfig: CapacitorElectronConfig; private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ new MenuItem({ label: 'Quit App', role: 'quit' }), ]; private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ { role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, { role: 'viewMenu' }, ]; private mainWindowState; private loadWebApp; private customScheme: string; constructor( capacitorFileConfig: CapacitorElectronConfig, trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[], appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[] ) { this.CapacitorFileConfig = capacitorFileConfig; this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron'; if (trayMenuTemplate) { this.TrayMenuTemplate = trayMenuTemplate; } if (appMenuBarMenuTemplate) { this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate; } // Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains. this.loadWebApp = electronServe({ directory: join(app.getAppPath(), 'app'), scheme: this.customScheme, }); } // Helper function to load in the app. private async loadMainWindow(thisRef: any) { await thisRef.loadWebApp(thisRef.MainWindow); } // Expose the mainWindow ref for use outside of the class. getMainWindow(): BrowserWindow { return this.MainWindow; } getCustomURLScheme(): string { return this.customScheme; } async init(): Promise { const icon = nativeImage.createFromPath( join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'icon.ico' : 'icon.png') ); const appName = "olcxja.miarven" app.setName(appName); app.setAppUserModelId(appName); this.mainWindowState = windowStateKeeper({ defaultWidth: 1000, defaultHeight: 800, }); // Setup preload script path and construct our main window. const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js'); this.MainWindow = new BrowserWindow({ icon, show: false, x: this.mainWindowState.x, y: this.mainWindowState.y, width: this.mainWindowState.width, height: this.mainWindowState.height, autoHideMenuBar: true, webPreferences: { nodeIntegration: true, contextIsolation: true, // Use preload to inject the electron varriant overrides for capacitor plugins. // preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"), preload: preloadPath, }, }); this.mainWindowState.manage(this.MainWindow); if (this.CapacitorFileConfig.backgroundColor) { this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor); } // If we close the main window with the splashscreen enabled we need to destory the ref. this.MainWindow.on('closed', () => { if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) { this.SplashScreen.getSplashWindow().close(); } }); // When the tray icon is enabled, setup the options. if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) { this.TrayIcon = new Tray(icon); this.TrayIcon.on('double-click', () => { if (this.MainWindow) { if (this.MainWindow.isVisible()) { this.MainWindow.hide(); } else { this.MainWindow.show(); this.MainWindow.focus(); } } }); this.TrayIcon.on('click', () => { if (this.MainWindow) { if (this.MainWindow.isVisible()) { this.MainWindow.hide(); } else { this.MainWindow.show(); this.MainWindow.focus(); } } }); this.TrayIcon.setToolTip(app.getName()); this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate)); } // Setup the main manu bar at the top of our window. //Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate)); Menu.setApplicationMenu(null); this.loadMainWindow(this); // Security this.MainWindow.webContents.setWindowOpenHandler((details) => { return { action: 'allow' }; }); this.MainWindow.webContents.on('will-navigate', (event, _newURL) => { if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) { event.preventDefault(); } }); // Link electron plugins into the system. setupCapacitorElectronPlugins(); // When the web app is loaded we hide the splashscreen if needed and show the mainwindow. this.MainWindow.webContents.on('dom-ready', () => { if (this.CapacitorFileConfig.electron?.splashScreenEnabled) { this.SplashScreen.getSplashWindow().hide(); } if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) { this.MainWindow.show(); } setTimeout(() => { CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', ''); }, 400); }); this.MainWindow.setMenu(null); this.MainWindow.setAutoHideMenuBar(true); this.MainWindow.removeMenu(); } } // Set a CSP up for our application based on the custom scheme export function setupContentSecurityPolicy(customScheme: string): void { session.defaultSession.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': [ electronIsDev ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:` : `default-src ${customScheme}://* 'unsafe-inline' data:`, ], }, }); }); }