Compare commits

...

1 Commits

Author SHA1 Message Date
dayuan.jiang
c083782802 fix: resolve Electron startup failures on Windows and Linux
- Try legacy port (61337) first to preserve existing users' localStorage,
  fall back to 13370 which is below the Windows Hyper-V ephemeral range (#705)
- Bind server and all URL references to 127.0.0.1 instead of localhost
  to fix IPv4/IPv6 mismatch on Linux (#684)
- Add OS-assigned port fallback (port 0) so startup never throws
- Log error codes in port checks for easier debugging
- Update localhost guards in index.ts and window-manager.ts to also
  match 127.0.0.1

Related: #705, #684
2026-02-26 22:36:07 +09:00
4 changed files with 46 additions and 13 deletions

View File

@@ -94,7 +94,8 @@ if (!gotTheLock) {
if (
url.includes("diagrams.net") ||
url.includes("draw.io") ||
url.startsWith("http://localhost")
url.startsWith("http://localhost") ||
url.startsWith("http://127.0.0.1")
) {
return { action: "allow" }
}

View File

@@ -68,7 +68,7 @@ export async function startNextServer(): Promise<string> {
const env: Record<string, string> = {
NODE_ENV: "production",
PORT: String(port),
HOSTNAME: "localhost",
HOSTNAME: "127.0.0.1",
// Enable Node.js built-in proxy support for fetch (Node.js 24+)
NODE_USE_ENV_PROXY: "1",
}

View File

@@ -9,9 +9,11 @@ import { app } from "electron"
const PORT_CONFIG = {
// Development mode uses fixed port for hot reload compatibility
development: 6002,
// Production mode uses fixed port (61337) to preserve localStorage
// Falls back to sequential ports if unavailable
production: 61337,
// Legacy production port — tried first to preserve localStorage for existing users
legacyProduction: 61337,
// New production port below the ephemeral range (49152-65535)
// to avoid conflicts with Windows Hyper-V / ephemeral port reservations
production: 13370,
// Maximum attempts to find an available port (fallback)
maxAttempts: 100,
}
@@ -27,7 +29,10 @@ let allocatedPort: number | null = null
export function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = net.createServer()
server.once("error", () => resolve(false))
server.once("error", (err: NodeJS.ErrnoException) => {
console.warn(`Port ${port} unavailable: ${err.code}`)
resolve(false)
})
server.once("listening", () => {
server.close()
resolve(true)
@@ -39,12 +44,12 @@ export function isPortAvailable(port: number): Promise<boolean> {
/**
* Find an available port
* - In development: uses fixed port (6002)
* - In production: uses fixed port (61337) to preserve localStorage
* - In production: uses fixed port (13370) to preserve localStorage
* - Falls back to sequential ports if preferred port is unavailable
* - Last resort: lets the OS assign a port (port 0)
*
* @param reuseExisting If true, try to reuse the previously allocated port
* @returns Promise<number> The available port
* @throws Error if no available port found after max attempts
*/
export async function findAvailablePort(reuseExisting = true): Promise<number> {
const isDev = !app.isPackaged
@@ -64,7 +69,16 @@ export async function findAvailablePort(reuseExisting = true): Promise<number> {
allocatedPort = null
}
// Try preferred port first
// In production, try legacy port first to preserve existing users' localStorage
if (!isDev) {
const legacyPort = PORT_CONFIG.legacyProduction
if (await isPortAvailable(legacyPort)) {
allocatedPort = legacyPort
return legacyPort
}
}
// Try preferred port
if (await isPortAvailable(preferredPort)) {
allocatedPort = preferredPort
return preferredPort
@@ -84,9 +98,23 @@ export async function findAvailablePort(reuseExisting = true): Promise<number> {
}
}
throw new Error(
`Failed to find available port after ${PORT_CONFIG.maxAttempts} attempts`,
// Last resort: let the OS pick an available port
console.warn(
"All sequential ports failed. Requesting OS-assigned port (localStorage may not persist across restarts).",
)
const osPort = await new Promise<number>((resolve, reject) => {
const server = net.createServer()
server.once("error", reject)
server.once("listening", () => {
const addr = server.address()
const port = (addr as net.AddressInfo).port
server.close(() => resolve(port))
})
server.listen(0, "127.0.0.1")
})
allocatedPort = osPort
console.log(`OS assigned port: ${osPort}`)
return osPort
}
/**
@@ -113,5 +141,5 @@ export function getServerUrl(): string {
"No port allocated yet. Call findAvailablePort() first.",
)
}
return `http://localhost:${allocatedPort}`
return `http://127.0.0.1:${allocatedPort}`
}

View File

@@ -66,7 +66,11 @@ export function createWindow(serverUrl: string): BrowserWindow {
// Handle page title updates
mainWindow.webContents.on("page-title-updated", (event, title) => {
if (title && !title.includes("localhost")) {
if (
title &&
!title.includes("localhost") &&
!title.includes("127.0.0.1")
) {
mainWindow?.setTitle(title)
} else {
event.preventDefault()