diff --git a/src/main/main.ts b/src/main/main.ts index e324513..f5daf78 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -19,6 +19,29 @@ import { resolveHtmlPath } from './util'; import { TWEET_INTENT } from './constants'; import { MadaraConfig } from './types'; import FireBaseService from './firebase'; +import { IpcMainInvokeEvent } from 'electron'; + + +let mainWindow: BrowserWindow | null = null; + +process.on('uncaughtException', (error) => { //Global error handler, to catch error outside IPChandler. Works when electronmon is off + console.error('Unhandled error from main process:', error); + + BrowserWindow.getAllWindows().forEach((win) => { + win.webContents.send('backend-error', error); + }); +}); + +function withErrorHandler(handler: (event: IpcMainInvokeEvent, ...args: any[]) => Promise) { //IPC error handler + return async function(event: IpcMainInvokeEvent, ...args: any[]) { + try { + return await handler(event, ...args); + } catch (error) { + console.error("Error in IPC handler:", error); + event.sender.send('backend-error', error); + } + }; +} class AppUpdater { constructor() { @@ -28,29 +51,28 @@ class AppUpdater { } } -let mainWindow: BrowserWindow | null = null; -ipcMain.handle('madara-start', async (event, config: MadaraConfig) => { +ipcMain.handle('madara-start', withErrorHandler(async (event, config: MadaraConfig) => { await Madara.start(mainWindow as BrowserWindow, config); -}); +})); -ipcMain.handle('madara-stop', async () => { +ipcMain.handle('madara-stop', withErrorHandler(async () => { await Madara.stop(); -}); +})); -ipcMain.handle('madara-delete', async () => { +ipcMain.handle('madara-delete', withErrorHandler(async () => { await Madara.deleteNode(); -}); +})); -ipcMain.handle('madara-setup', async (event, config: MadaraConfig) => { +ipcMain.handle('madara-setup', withErrorHandler(async (event, config: MadaraConfig) => { await Madara.setup(mainWindow as BrowserWindow, config); -}); +})); -ipcMain.handle('release-exists', async (event, config: MadaraConfig) => { +ipcMain.handle('release-exists', withErrorHandler(async (event, config: MadaraConfig) => { return Madara.releaseExists(config); -}); +})); -ipcMain.handle('send-tweet', async () => { +ipcMain.handle('send-tweet', withErrorHandler(async () => { await Madara.getCurrentWindowScreenshot(mainWindow as BrowserWindow); const file = await Madara.fetchScreenshotFromSystem(); @@ -66,42 +88,43 @@ ipcMain.handle('send-tweet', async () => { // open link in browser shell.openExternal(TWEET_INTENT + shortenedLink); -}); +})); -ipcMain.handle('child-process-in-memory', (): boolean => { +ipcMain.handle('child-process-in-memory', withErrorHandler(async (): Promise => { return Madara.childProcessInMemory(); -}); +})); -ipcMain.handle('madara-app-download', async (event, appId: string) => { +ipcMain.handle('madara-app-download', withErrorHandler(async (event, appId: string) => { await MadaraApp.downloadApp(mainWindow as BrowserWindow, appId); -}); +})); -ipcMain.handle('madara-installed-apps', () => { - return MadaraApp.getInstalledApps(); -}); +ipcMain.handle('madara-installed-apps', withErrorHandler(async () => { + return Promise.resolve(MadaraApp.getInstalledApps()); +})); -ipcMain.handle('madara-app-start', (event, appId: string) => { - return MadaraApp.startApp(mainWindow as BrowserWindow, appId); -}); +ipcMain.handle('madara-app-start', withErrorHandler(async (event, appId: string) => { + return await MadaraApp.startApp(mainWindow as BrowserWindow, appId); +})); -ipcMain.handle('madara-app-stop', (event, appId: string) => { +ipcMain.handle('madara-app-stop', withErrorHandler(async (event, appId: string) => { return MadaraApp.stopApp(mainWindow as BrowserWindow, appId); -}); +})); ipcMain.handle( 'madara-app-update-settings', - (event, appId: string, settings: any) => { + withErrorHandler(async (event, appId: string, settings: any) => { return MadaraApp.updateAppSettings(appId, settings); - } + }) ); -ipcMain.handle('madara-app-get-settings', (event, appId: string) => { +ipcMain.handle('madara-app-get-settings', withErrorHandler(async (event, appId: string) => { return MadaraApp.getAppSettings(appId); -}); +})); -ipcMain.handle('madara-fetch-all-running-apps', () => { +ipcMain.handle('madara-fetch-all-running-apps', withErrorHandler(async () => { return MadaraApp.fetchAllRunningApps(mainWindow as BrowserWindow); -}); +})); + if (process.env.NODE_ENV === 'production') { const sourceMapSupport = require('source-map-support'); diff --git a/src/main/preload.ts b/src/main/preload.ts index 3bc6987..2280111 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -42,6 +42,12 @@ const electronHandler = { fetchAllRunningApps: () => ipcRenderer.invoke('madara-fetch-all-running-apps'), }, + onUnhandledError: (callback: (message: string) => void) => + ipcRenderer.on('backend-error', (event, message) => callback(message)), + offUnhandledError: (callback: (message: string) => void) => + ipcRenderer.removeListener('backend-error', (event, message) => + callback(message) + ), }, }; diff --git a/src/renderer/features/appsSlice.ts b/src/renderer/features/appsSlice.ts index 573f2de..3d138ee 100644 --- a/src/renderer/features/appsSlice.ts +++ b/src/renderer/features/appsSlice.ts @@ -68,15 +68,21 @@ export const updateAppRunningStatus = }; export const fetchAndSetRunningApps = () => async (dispatch: any) => { - const fetchedApps = - await window.electron.ipcRenderer.madaraApp.fetchAllRunningApps(); - const appsObject = fetchedApps.reduce((acc: any, app: any) => { - acc[app.id] = true; - return acc; - }, {}); - dispatch(setRunningApps(appsObject)); + const fetchedApps = await window.electron.ipcRenderer.madaraApp.fetchAllRunningApps(); + + // Check if fetchedApps is defined and it's an array. + if (Array.isArray(fetchedApps)) { + const appsObject = fetchedApps.reduce((acc: any, app: any) => { + acc[app.id] = true; + return acc; + }, {}); + dispatch(setRunningApps(appsObject)); + } else { + console.error('Failed to fetch running apps:', fetchedApps); + } }; + export const setupInstalledApps = () => async (dispatch: any) => { const installedApps = await window.electron.ipcRenderer.madaraApp.installedApps(); diff --git a/src/renderer/hooks/useErrorBoundaryMain.ts b/src/renderer/hooks/useErrorBoundaryMain.ts new file mode 100644 index 0000000..1cb4018 --- /dev/null +++ b/src/renderer/hooks/useErrorBoundaryMain.ts @@ -0,0 +1,31 @@ +import { useEffect } from 'react'; +import { showSnackbar } from 'renderer/store/snackbar'; +import { useAppDispatch } from 'renderer/utils/hooks'; + +// Global variable to check if the listener has already been attached +let hasListenerBeenAttached = false; + +function useErrorBoundaryMain() { + const dispatch = useAppDispatch(); + + useEffect(() => { + if (hasListenerBeenAttached) { + // If the listener is already attached, no need to attach it again + return; + } + + function handleUnhandledError(message: any) { + console.error('Unhandled error from main process:', message); + dispatch(showSnackbar('Something went wrong')); + } + + window.electron.ipcRenderer.onUnhandledError(handleUnhandledError); + hasListenerBeenAttached = true; + + return () => { //clean mount + window.electron.ipcRenderer.offUnhandledError(handleUnhandledError); + }; + }, [dispatch]); +} + +export default useErrorBoundaryMain; diff --git a/src/renderer/pages/Landing.tsx b/src/renderer/pages/Landing.tsx index 9ea98c2..b6d0d40 100644 --- a/src/renderer/pages/Landing.tsx +++ b/src/renderer/pages/Landing.tsx @@ -22,6 +22,7 @@ import Button from '../components/Button'; import InfiniteBarLoader from '../components/InfiniteBarLoader'; import Input from '../components/Input'; import SharinganEye from '../components/SharinganEye'; +import useErrorBoundaryMain from 'renderer/hooks/useErrorBoundaryMain'; const LandingContainer = styled(motion.div)` background-color: black; @@ -190,6 +191,8 @@ export default function Landing() { ); }; + useErrorBoundaryMain(); + return (