import { app, BrowserWindow, ipcMain, Menu, MenuItem, MenuItemConstructorOptions, shell, Tray } from "electron"; import { release } from "node:os"; import node_path, { join } from "node:path"; import BeanFactory from "../core/BeanFactory"; import ServerRepository from "../repository/ServerRepository"; import VersionRepository from "../repository/VersionRepository"; import ProxyRepository from "../repository/ProxyRepository"; import SystemService from "../service/SystemService"; import ServerService from "../service/ServerService"; import GitHubService from "../service/GitHubService"; import VersionService from "../service/VersionService"; import LogService from "../service/LogService"; import FrpcProcessService from "../service/FrpcProcessService"; import ProxyService from "../service/ProxyService"; import ConfigController from "../controller/ConfigController"; import VersionController from "../controller/VersionController"; import LogController from "../controller/LogController"; import LaunchController from "../controller/LaunchController"; import ProxyController from "../controller/ProxyController"; import SystemController from "../controller/SystemController"; import { ipcRouters, listeners } from "../core/IpcRouter"; import Logger from "../core/Logger"; process.env.DIST_ELECTRON = join(__dirname, ".."); process.env.DIST = join(process.env.DIST_ELECTRON, "../dist"); process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL ? join(process.env.DIST_ELECTRON, "../public") : process.env.DIST; const preload = join(__dirname, "../preload/index.js"); const url = process.env.VITE_DEV_SERVER_URL; const indexHtml = join(process.env.DIST, "index.html"); class FrpcDesktopApp { private _win: BrowserWindow | null = null; private readonly _silentStart = false; private _quitting = false; constructor() { this.initializeBeans(); this.initializeListeners(); this.initializeRouters(); this.initializeElectronApp(); } initializeWindow() { if (this._win) { return; } this._win = new BrowserWindow({ title: app.getName(), icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"), width: 800, height: 600, minWidth: 800, minHeight: 600, maxWidth: 1280, maxHeight: 960, webPreferences: { preload, // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production // Consider using contextBridge.exposeInMainWorld // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation nodeIntegration: true, contextIsolation: false }, show: true }); BeanFactory.setBean("win", this._win); if (process.env.VITE_DEV_SERVER_URL) { // electron-vite-vue#298 this._win.loadURL(url).then(() => {}); // Open devTool if the app is not packaged this._win.webContents.openDevTools(); } else { this._win.loadFile(indexHtml).then(() => {}); } this._win.webContents.on("did-finish-load", () => { this._win?.webContents.send( "main-process-message", new Date().toLocaleString() ); }); this._win.webContents.setWindowOpenHandler(({ url }) => { if (url.startsWith("https:")) shell.openExternal(url); return { action: "deny" }; }); const { Menu } = require("electron"); Menu.setApplicationMenu(null); const that = this; this._win.on("minimize", function (event) { event.preventDefault(); that._win.hide(); }); this._win.on("close", function (event) { if (!that._quitting) { event.preventDefault(); that._win.hide(); if (process.platform === "darwin") { app.dock.hide(); } } return false; }); Logger.info( `FrpcDesktopApp.initializeWindow`, `Window initialized.` ); } initializeTray() { const that = this; let menu: Array = [ { label: "显示主窗口", click: function () { that._win.show(); if (process.platform === "darwin") { app.dock.show().then(() => {}); } } }, { label: "退出", click: () => { that._quitting = true; // todo stop frpc process app.quit(); } } ]; const tray = new Tray( node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png") ); tray.setToolTip(app.getName()); const contextMenu = Menu.buildFromTemplate(menu); tray.setContextMenu(contextMenu); // 托盘双击打开 tray.on("double-click", () => { this._win.show(); }); Logger.info( `FrpcDesktopApp.initializeTray`, `Tray initialized.` ); } initializeElectronApp() { // Disable GPU Acceleration for Windows 7 if (release().startsWith("6.1")) app.disableHardwareAcceleration(); // Set application name for Windows 10+ notifications if (process.platform === "win32") app.setAppUserModelId(app.getName()); if (!app.requestSingleInstanceLock()) { app.quit(); process.exit(0); } app.whenReady().then(() => { this.initializeWindow(); this.initializeTray(); // initLog(); // logInfo( // LogModule.APP, // `Application started. Current system architecture: ${ // process.arch // }, platform: ${process.platform}, version: ${app.getVersion()}.` // ); // getConfig((err, config) => { // if (err) { // logError(LogModule.APP, `Failed to get config: ${err.message}`); // return; // } // createWindow(config) // .then(r => { // logInfo(LogModule.APP, `Window created successfully.`); // createTray(config); // // // if (config) { // // logInfo( // // LogModule.APP, // // `Config retrieved: ${JSON.stringify( // // maskSensitiveData(config, [ // // "serverAddr", // // "serverPort", // // "authToken", // // "user", // // "metaToken" // // ]) // // )}` // // ); // // // // if (config.systemStartupConnect) { // // startFrpWorkerProcess(config); // // } // // } // // const ipcRouterConfig = new IpcRouterConfigurate(win); // // Initialize APIs // // try { // // initGitHubApi(win); // // logInfo(LogModule.APP, `GitHub API initialized.`); // // // // initConfigApi(win); // // logInfo(LogModule.APP, `Config API initialized.`); // // // // initFileApi(); // // logInfo(LogModule.APP, `File API initialized.`); // // // // // initUpdaterApi(win); // // logInfo(LogModule.APP, `Updater API initialization skipped.`); // // } catch (error) { // // logError( // // LogModule.APP, // // `Error during API initialization: ${error.message}` // // ); // // } // }) // .catch(error => { // logError(LogModule.APP, `Error creating window: ${error.message}`); // }); // }); }); app.on("window-all-closed", () => { // logInfo(LogModule.APP, `All windows closed.`); this._win = null; if (process.platform !== "darwin") { // todo stop frpc process // stopFrpcProcess(() => { // logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`); app.quit(); // }); } }); app.on("second-instance", () => { if (this._win) { if (this._win.isMinimized()) this._win.restore(); this._win.focus(); } }); app.on("activate", () => { // logInfo(LogModule.APP, `Application activated.`); const allWindows = BrowserWindow.getAllWindows(); if (allWindows.length) { allWindows[0].focus(); } else { this.initializeWindow(); } }); app.on("before-quit", () => { // todo stop frpc process this._quitting = true; }); Logger.info( `FrpcDesktopApp.initializeElectronApp`, `ElectronApp initialized.` ); } initializeBeans() { BeanFactory.setBean("serverRepository", new ServerRepository()); BeanFactory.setBean("versionRepository", new VersionRepository()); BeanFactory.setBean("proxyRepository", new ProxyRepository()); BeanFactory.setBean("systemService", new SystemService()); BeanFactory.setBean( "serverService", new ServerService( BeanFactory.getBean("serverRepository"), BeanFactory.getBean("proxyRepository") ) ); BeanFactory.setBean("gitHubService", new GitHubService()); BeanFactory.setBean( "versionService", new VersionService( BeanFactory.getBean("versionRepository"), BeanFactory.getBean("systemService"), BeanFactory.getBean("gitHubService") ) ); BeanFactory.setBean( "logService", new LogService(BeanFactory.getBean("systemService")) ); BeanFactory.setBean( "frpcProcessService", new FrpcProcessService( BeanFactory.getBean("serverService"), BeanFactory.getBean("versionRepository") ) ); BeanFactory.setBean( "proxyService", new ProxyService(BeanFactory.getBean("proxyRepository")) ); BeanFactory.setBean( "configController", new ConfigController( BeanFactory.getBean("serverService"), BeanFactory.getBean("systemService"), BeanFactory.getBean("frpcProcessService") ) ); BeanFactory.setBean( "versionController", new VersionController( BeanFactory.getBean("versionService"), BeanFactory.getBean("versionRepository") ) ); BeanFactory.setBean( "logController", new LogController(BeanFactory.getBean("logService")) ); BeanFactory.setBean( "launchController", new LaunchController(BeanFactory.getBean("frpcProcessService")) ); BeanFactory.setBean( "proxyController", new ProxyController( BeanFactory.getBean("proxyService"), BeanFactory.getBean("proxyRepository") ) ); BeanFactory.setBean( "systemController", new SystemController(BeanFactory.getBean("systemService")) ); Logger.info( `FrpcDesktopApp.initializeBeans`, `Beans initialized.` ); } /** * initJob * @private */ private initializeListeners() { Object.keys(listeners).forEach(listenerKey => { console.log(listenerKey, "listenerKey", listeners[listenerKey]); const { listenerMethod, channel } = listeners[listenerKey]; const [beanName, method] = listenerMethod.split("."); const bean = BeanFactory.getBean(beanName); const listenerParam: ListenerParam = { // win: BeanFactory.getBean("win"), channel: channel, args: [] }; bean[method].call(bean, listenerParam); }); Logger.info( `FrpcDesktopApp.initializeListeners`, `Listeners initialized.` ); // this._beans.get("logService").watchFrpcLog(this._win); } /** * initRouters * @private */ private initializeRouters() { Object.keys(ipcRouters).forEach(routerKey => { const routerGroup = ipcRouters[routerKey]; Object.keys(routerGroup).forEach(method => { const router = routerGroup[method]; ipcMain.on(router.path, (event, args) => { const req: ControllerParam = { // win: BeanFactory.getBean("win"), channel: `${router.path}:hook`, event: event, args: args }; const [beanName, method] = router.controller.split("."); const bean = BeanFactory.getBean(beanName); bean[method].call(bean, req); Logger.debug( `ipcRouter`, `path: ${router.path} + req: (channel: ${ req.channel }, args: ${JSON.stringify( req.args )}) => bean: ${beanName}.${method}` ); }); }); }); Logger.info( `FrpcDesktopApp.initializeRouters`, `Routers initialized.` ); } } new FrpcDesktopApp();