172 lines
6.4 KiB
TypeScript
172 lines
6.4 KiB
TypeScript
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
|
|
import path from "node:path";
|
|
import type { AppInfo, FlowDefinition } from "../shared/types";
|
|
import "./capabilities";
|
|
import { catalogPath, loadCatalog } from "./catalog";
|
|
import { createDemoFlow, createExampleFlows, createFlow, deleteFlow, getFlow, listFlows, saveFlow } from "./flows";
|
|
import { capabilityCount, capabilityIds } from "./engine/registry";
|
|
import { closeBrowserSession, startRun, stopRun } from "./run/manager";
|
|
import { fixtureUrl, startFixtureServer, stopFixtureServer } from "./fixture/server";
|
|
import { logMain } from "./logger";
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
|
|
|
if (process.env.RPA_ENABLE_GPU !== "1") app.disableHardwareAcceleration();
|
|
process.on("uncaughtException", (error) => logMain("uncaughtException", error.stack || error.message));
|
|
process.on("unhandledRejection", (reason) => logMain("unhandledRejection", reason instanceof Error ? reason.stack || reason.message : String(reason)));
|
|
|
|
function createWindow(): void {
|
|
const e2e = process.env.RPA_E2E === "1";
|
|
logMain("createWindow", { packaged: app.isPackaged, appPath: app.getAppPath(), userData: app.getPath("userData") });
|
|
mainWindow = new BrowserWindow({
|
|
width: 1440,
|
|
height: 900,
|
|
minWidth: 1200,
|
|
minHeight: 760,
|
|
show: false,
|
|
backgroundColor: "#E7EDF3",
|
|
title: "RPA Studio",
|
|
titleBarStyle: "hidden",
|
|
titleBarOverlay: {
|
|
color: "#FFFFFF",
|
|
symbolColor: "#4E5A6B",
|
|
height: 58
|
|
},
|
|
webPreferences: {
|
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false
|
|
}
|
|
});
|
|
|
|
let shown = false;
|
|
const showWindow = (source: string) => {
|
|
if (e2e || shown || !mainWindow || mainWindow.isDestroyed()) return;
|
|
shown = true;
|
|
logMain("showWindow", source);
|
|
mainWindow.show();
|
|
mainWindow.focus();
|
|
};
|
|
const showFallback = setTimeout(() => showWindow("fallback-timeout"), 5000);
|
|
mainWindow.on("ready-to-show", () => { clearTimeout(showFallback); showWindow("ready-to-show"); });
|
|
mainWindow.on("unresponsive", () => logMain("window-unresponsive"));
|
|
mainWindow.on("responsive", () => logMain("window-responsive"));
|
|
mainWindow.on("closed", () => {
|
|
mainWindow = null;
|
|
});
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
if (/^https?:/i.test(url)) void shell.openExternal(url);
|
|
return { action: "deny" };
|
|
});
|
|
mainWindow.webContents.on("did-fail-load", (_event, code, description, url) => {
|
|
logMain("did-fail-load", { code, description, url });
|
|
showWindow("did-fail-load");
|
|
});
|
|
mainWindow.webContents.on("render-process-gone", (_event, details) => logMain("render-process-gone", details));
|
|
mainWindow.webContents.on("console-message", (_event, level, message, line, sourceId) => {
|
|
if (level >= 2) logMain("renderer-console", { level, message, line, sourceId });
|
|
});
|
|
|
|
const devUrl = process.env.ELECTRON_RENDERER_URL;
|
|
if (devUrl) {
|
|
void mainWindow.loadURL(devUrl);
|
|
} else {
|
|
void mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
}
|
|
}
|
|
|
|
function registerIpc(): void {
|
|
ipcMain.handle("app:info", (): AppInfo => ({
|
|
version: app.getVersion(),
|
|
electron: process.versions.electron,
|
|
chrome: process.versions.chrome,
|
|
node: process.versions.node,
|
|
userData: app.getPath("userData"),
|
|
catalogPath: catalogPath(),
|
|
packaged: app.isPackaged,
|
|
fixtureUrl: fixtureUrl()
|
|
}));
|
|
|
|
ipcMain.handle("catalog:get", () => loadCatalog());
|
|
ipcMain.handle("run:start", (event, flow: FlowDefinition) => startRun(flow, event.sender));
|
|
ipcMain.handle("run:stop", (_event, runId?: string) => stopRun(runId));
|
|
ipcMain.handle("run:capabilities", () => ({ count: capabilityCount(), ids: capabilityIds() }));
|
|
ipcMain.handle("flows:list", () => listFlows());
|
|
ipcMain.handle("flows:create", (_event, name?: string) => {
|
|
const flow = createFlow(name);
|
|
return saveFlow(flow);
|
|
});
|
|
ipcMain.handle("flows:create-demo", () => {
|
|
const url = fixtureUrl();
|
|
return saveFlow(url ? createDemoFlow(url) : createFlow("未命名任务"));
|
|
});
|
|
ipcMain.handle("flows:create-examples", () => {
|
|
const url = fixtureUrl();
|
|
if (!url) throw new Error("本地演示站未启动");
|
|
for (const flow of createExampleFlows(url)) saveFlow(flow);
|
|
return listFlows();
|
|
});
|
|
ipcMain.handle("flows:get", (_event, id: string) => getFlow(id));
|
|
ipcMain.handle("flows:save", (_event, flow: FlowDefinition) => saveFlow(flow));
|
|
ipcMain.handle("flows:delete", (_event, id: string) => {
|
|
deleteFlow(id);
|
|
return true;
|
|
});
|
|
|
|
ipcMain.handle("dialog:open", async (_event, options: { title?: string; filters?: Electron.FileFilter[]; directories?: boolean }) => {
|
|
if (!mainWindow) return null;
|
|
const result = await dialog.showOpenDialog(mainWindow, {
|
|
title: options?.title || "选择文件",
|
|
properties: options?.directories ? ["openDirectory"] : ["openFile"],
|
|
filters: options?.filters
|
|
});
|
|
return result.canceled ? null : result.filePaths[0] || null;
|
|
});
|
|
|
|
ipcMain.handle("dialog:save", async (_event, options: { title?: string; defaultPath?: string; filters?: Electron.FileFilter[] }) => {
|
|
if (!mainWindow) return null;
|
|
const result = await dialog.showSaveDialog(mainWindow, {
|
|
title: options?.title || "保存文件",
|
|
defaultPath: options?.defaultPath,
|
|
filters: options?.filters
|
|
});
|
|
return result.canceled ? null : result.filePath || null;
|
|
});
|
|
}
|
|
|
|
const gotLock = app.requestSingleInstanceLock();
|
|
if (!gotLock) {
|
|
app.quit();
|
|
} else {
|
|
app.on("second-instance", () => {
|
|
if (!mainWindow) return;
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
mainWindow.focus();
|
|
});
|
|
|
|
app.whenReady().then(async () => {
|
|
logMain("app-ready", { electron: process.versions.electron, chrome: process.versions.chrome, node: process.versions.node });
|
|
try {
|
|
await startFixtureServer(Number(process.env.RPA_FIXTURE_PORT || 17863));
|
|
} catch (error) {
|
|
console.warn("local fixture server unavailable", error);
|
|
logMain("fixture-server-failed", error instanceof Error ? error.stack || error.message : String(error));
|
|
}
|
|
registerIpc();
|
|
createWindow();
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
});
|
|
});
|
|
|
|
app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") app.quit();
|
|
});
|
|
|
|
app.on("before-quit", () => {
|
|
void closeBrowserSession();
|
|
void stopFixtureServer();
|
|
});
|
|
}
|