diff --git a/src/main/main.ts b/src/main/main.ts index 45550e1c9..4199bdd88 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -93,12 +93,15 @@ const installExtensions = async () => { const forceDownload = !!process.env.UPGRADE_EXTENSIONS const extensions = ['REACT_DEVELOPER_TOOLS'] - return installer - .default( + try { + return await installer.default( extensions.map((name) => installer[name as keyof typeof Installer]), forceDownload, ) - .catch((err: unknown) => logger.error(getErrorMessage(err))) + } catch (error) { + logger.warn(`Skipping development extension installation: ${getErrorMessage(error)}`) + return [] + } } const createMainWindow = async () => { diff --git a/src/main/menu.ts b/src/main/menu.ts index 44efa68d9..57dbda239 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -19,6 +19,24 @@ interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { export default class MenuBuilder { private mainWindow: BrowserWindow private projectService: ProjectService + private readonly handleDevelopmentContextMenu = ( + _: Electron.Event, + props: Electron.ContextMenuParams, + ): void => { + if (!this.hasLiveWindow()) return + + const { x, y } = props + + Menu.buildFromTemplate([ + { + label: 'Inspect element', + click: () => { + if (!this.hasLiveWindow()) return + this.mainWindow.webContents.inspectElement(x, y) + }, + }, + ]).popup({ window: this.mainWindow }) + } developOptions: MenuItemConstructorOptions[] = [ { type: 'separator' }, @@ -32,7 +50,19 @@ export default class MenuBuilder { this.projectService = new ProjectService(mainWindow) } + private hasLiveWindow(): boolean { + return !this.mainWindow.isDestroyed() + } + + private getFallbackMenu(): Menu { + return Menu.getApplicationMenu() ?? Menu.buildFromTemplate([]) + } + async buildMenu(): Promise { + if (!this.hasLiveWindow()) { + return this.getFallbackMenu() + } + if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { this.setupDevelopmentEnvironment() } @@ -129,26 +159,22 @@ export default class MenuBuilder { */ setupDevelopmentEnvironment(): void { - this.mainWindow.webContents.on('context-menu', (_, props) => { - const { x, y } = props + if (!this.hasLiveWindow()) return - Menu.buildFromTemplate([ - { - label: 'Inspect element', - click: () => { - this.mainWindow.webContents.inspectElement(x, y) - }, - }, - ]).popup({ window: this.mainWindow }) - }) + this.mainWindow.webContents.removeListener('context-menu', this.handleDevelopmentContextMenu) + this.mainWindow.webContents.on('context-menu', this.handleDevelopmentContextMenu) } updateAppTheme() { const newTheme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark' nativeTheme.themeSource = newTheme store.set('theme', newTheme) - this.mainWindow.webContents.send('system:update-theme') - void this.buildMenu() + if (this.hasLiveWindow()) { + this.mainWindow.webContents.send('system:update-theme') + } + void this.buildMenu().catch((error) => { + console.error('Error rebuilding application menu:', error) + }) } /** diff --git a/src/main/modules/ipc/main.ts b/src/main/modules/ipc/main.ts index 7b131778a..1cbf13048 100644 --- a/src/main/modules/ipc/main.ts +++ b/src/main/modules/ipc/main.ts @@ -952,7 +952,11 @@ class MainProcessBridge implements MainIpcModule { this.simulatorModule.stop() this.mainWindow?.webContents.reload() } - handleWindowRebuildMenu = () => void this.menuBuilder.buildMenu() + handleWindowRebuildMenu = () => { + void this.menuBuilder.buildMenu().catch((error) => { + console.error('Error rebuilding application menu:', error) + }) + } // Hardware handlers handleHardwareGetAvailableCommunicationPorts = async () => this.hardwareModule.getAvailableSerialPorts()