mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-05 01:07:56 +08:00
- Apply preset env after app ready, so Windows/Linux get decrypted keys - Never re-encrypt ciphertext; restore env when switching or removing presets - Block navigation away from the app, open external links in the browser, check IPC senders - Keep inherited proxy settings, default NO_PROXY for localhost - Serialize server start/restart, kill stuck processes, follow port changes - Atomic config writes, keep corrupt files as backups, remember the server port - Menu and settings window stay in sync; dev script gets the decrypted preset env - Use app.isPackaged, parse inline .env comments, drop .env files from the bundle
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
import { app, BrowserWindow, dialog, shell } from "electron"
|
|
import { buildAppMenu } from "./app-menu"
|
|
import { applyCurrentPresetToEnv } from "./config-manager"
|
|
import { loadEnvFile } from "./env-loader"
|
|
import { registerIpcHandlers } from "./ipc-handlers"
|
|
import { startNextServer, stopNextServer } from "./next-server"
|
|
import { applyProxyToEnv } from "./proxy-manager"
|
|
import { registerSettingsWindowHandlers } from "./settings-window"
|
|
import {
|
|
createWindow,
|
|
getAppUrl,
|
|
getMainWindow,
|
|
isAppUrl,
|
|
} from "./window-manager"
|
|
|
|
// Single instance lock
|
|
const gotTheLock = app.requestSingleInstanceLock()
|
|
|
|
if (!gotTheLock) {
|
|
app.quit()
|
|
} else {
|
|
app.on("second-instance", () => {
|
|
const mainWindow = getMainWindow()
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) mainWindow.restore()
|
|
mainWindow.focus()
|
|
}
|
|
})
|
|
|
|
// Load environment variables from .env files
|
|
loadEnvFile()
|
|
|
|
// Apply proxy settings from saved config
|
|
applyProxyToEnv()
|
|
|
|
const isDev = !app.isPackaged
|
|
|
|
app.whenReady().then(async () => {
|
|
// Apply saved preset environment variables (overrides .env).
|
|
// Must run after ready: on Windows and Linux safeStorage can't
|
|
// decrypt the API key before that.
|
|
applyCurrentPresetToEnv()
|
|
|
|
// Register IPC handlers
|
|
registerIpcHandlers()
|
|
registerSettingsWindowHandlers()
|
|
|
|
// Build application menu
|
|
buildAppMenu()
|
|
|
|
try {
|
|
let serverUrl: string
|
|
if (isDev) {
|
|
// Development: use the dev server URL
|
|
serverUrl =
|
|
process.env.ELECTRON_DEV_URL || "http://localhost:6002"
|
|
console.log(`Development mode: connecting to ${serverUrl}`)
|
|
} else {
|
|
// Production: start Next.js standalone server
|
|
serverUrl = await startNextServer()
|
|
}
|
|
|
|
// Create main window
|
|
createWindow(serverUrl)
|
|
} catch (error) {
|
|
console.error("Failed to start application:", error)
|
|
dialog.showErrorBox(
|
|
"Startup Error",
|
|
`Failed to start the application: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
)
|
|
app.quit()
|
|
}
|
|
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
const appUrl = getAppUrl()
|
|
if (appUrl) {
|
|
createWindow(appUrl)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") {
|
|
stopNextServer()
|
|
app.quit()
|
|
}
|
|
})
|
|
|
|
app.on("before-quit", () => {
|
|
stopNextServer()
|
|
})
|
|
|
|
// Pages allowed inside app windows: the app server and draw.io
|
|
const isInAppUrl = (url: string): boolean => {
|
|
if (isAppUrl(url)) return true
|
|
try {
|
|
const { hostname } = new URL(url)
|
|
return ["diagrams.net", "draw.io"].some(
|
|
(domain) =>
|
|
hostname === domain || hostname.endsWith(`.${domain}`),
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
const isWebUrl = (url: string): boolean =>
|
|
url.startsWith("http://") || url.startsWith("https://")
|
|
|
|
// Open external links in default browser
|
|
app.on("web-contents-created", (_, contents) => {
|
|
contents.setWindowOpenHandler(({ url }) => {
|
|
if (isInAppUrl(url)) {
|
|
return { action: "allow" }
|
|
}
|
|
// Open other links in external browser
|
|
if (isWebUrl(url)) {
|
|
shell.openExternal(url)
|
|
return { action: "deny" }
|
|
}
|
|
return { action: "allow" }
|
|
})
|
|
|
|
// Clicking a plain link would otherwise replace the app page with
|
|
// an external site that keeps the preload API
|
|
contents.on("will-navigate", (event) => {
|
|
if (isInAppUrl(event.url)) {
|
|
return
|
|
}
|
|
event.preventDefault()
|
|
if (isWebUrl(event.url)) {
|
|
shell.openExternal(event.url)
|
|
}
|
|
})
|
|
})
|
|
}
|