feat: Implement application bootstrap and window management

- Added bootstrap functionality to initialize the Electron application.
- Created a new paths module to manage application paths and URLs.
- Introduced a shutdown service to handle graceful application shutdowns.
- Refactored error logging to use a dedicated logger module.
- Implemented process killing logic for NodeCG processes across platforms.
- Established navigation policies for internal and external URL handling in windows.
- Developed window service for creating and managing application windows.
- Added tests for application paths, application controller, navigation policies, process killer, and shutdown service.
This commit is contained in:
2026-05-24 16:14:23 +02:00
parent c168c3b84a
commit e3d3936156
17 changed files with 1067 additions and 264 deletions
+123
View File
@@ -0,0 +1,123 @@
import { BrowserWindow, BrowserWindowConstructorOptions, shell } from "electron";
import { AppRuntimeConfig } from "../config/runtime-config";
import { DEFAULT_WINDOW_BACKGROUND, DEFAULT_WINDOW_SIZE, LOADING_WINDOW_SIZE } from "../constants";
import { resolveAppIconPath } from "./icon-path";
import { shouldAllowInternalNavigation, shouldOpenExternalNavigation } from "./navigation-policy";
type WindowServiceDependencies = {
appConfig: AppRuntimeConfig;
allowDevTools: boolean;
rootPath: string;
mainDashboardUrl: string;
};
export function createMainWindow({
allowDevTools,
appConfig,
rootPath,
mainDashboardUrl,
}: WindowServiceDependencies): BrowserWindow {
const windowOptions = createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: false });
const window = new BrowserWindow(windowOptions);
denyPermissionsByDefault(window);
window.setMenuBarVisibility(false);
window.webContents.setWindowOpenHandler(({ url }) => {
if (shouldOpenExternalNavigation(url)) {
void shell.openExternal(url);
}
return { action: "deny" };
});
window.webContents.on("will-navigate", (event, url) => {
if (shouldAllowInternalNavigation(url, mainDashboardUrl)) {
return;
}
event.preventDefault();
if (shouldOpenExternalNavigation(url)) {
void shell.openExternal(url);
}
});
window.on("page-title-updated", (event) => {
event.preventDefault();
});
return window;
}
export function createLoadingWindow({
allowDevTools,
appConfig,
rootPath,
}: Omit<WindowServiceDependencies, "mainDashboardUrl">): BrowserWindow {
const window = new BrowserWindow(createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: true }));
denyPermissionsByDefault(window);
window.on("page-title-updated", (event) => {
event.preventDefault();
});
return window;
}
export function createWindowOptions({
allowDevTools,
appConfig,
rootPath,
isLoadingWindow,
}: {
allowDevTools: boolean;
appConfig: AppRuntimeConfig;
rootPath: string;
isLoadingWindow: boolean;
}): BrowserWindowConstructorOptions {
const iconPath = resolveAppIconPath(appConfig, rootPath);
const baseOptions: BrowserWindowConstructorOptions = {
show: false,
title: appConfig.title,
...(iconPath ? { icon: iconPath } : {}),
backgroundColor: DEFAULT_WINDOW_BACKGROUND,
webPreferences: {
contextIsolation: true,
devTools: allowDevTools,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
},
};
if (isLoadingWindow) {
return {
...baseOptions,
frame: false,
width: LOADING_WINDOW_SIZE.width,
height: LOADING_WINDOW_SIZE.height,
resizable: false,
movable: true,
minimizable: false,
maximizable: false,
};
}
return {
...baseOptions,
width: DEFAULT_WINDOW_SIZE.width,
height: DEFAULT_WINDOW_SIZE.height,
minWidth: DEFAULT_WINDOW_SIZE.minWidth,
minHeight: DEFAULT_WINDOW_SIZE.minHeight,
};
}
function denyPermissionsByDefault(window: BrowserWindow): void {
window.webContents.session.setPermissionRequestHandler((_webContents, _permission, callback) => {
callback(false);
});
}