Files
2026-03-18 13:05:14 -04:00

135 lines
3.6 KiB
JavaScript

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
const devServerUrl = process.env.ELECTRON_DEV_SERVER_URL || process.env.VITE_DEV_SERVER_URL || ''
const isDev = !app.isPackaged && Boolean(devServerUrl)
const API_URL_KEY = 'runtime_api_url'
const DEFAULT_API_URL = process.env.VITE_API_URL || 'http://localhost:4000/api'
let mainWindow
function resolveRendererEntry() {
const candidates = [
path.join(__dirname, '..', 'dist', 'index.html'),
path.join(process.resourcesPath || '', 'app.asar', 'dist', 'index.html'),
path.join(process.resourcesPath || '', 'app.asar.unpacked', 'dist', 'index.html'),
]
for (const candidate of candidates) {
if (candidate && fs.existsSync(candidate)) return candidate
}
return candidates[0]
}
function normalizeApiUrl(value) {
if (!value || typeof value !== 'string') return null
const trimmed = value.trim().replace(/\/+$/, '')
if (!trimmed) return null
if (!/^https?:\/\//i.test(trimmed)) return null
return /\/api$/i.test(trimmed) ? trimmed : `${trimmed}/api`
}
function getStoragePath() {
return path.join(app.getPath('userData'), 'storage.json')
}
function readStorage() {
try {
const p = getStoragePath()
if (!fs.existsSync(p)) return {}
const raw = fs.readFileSync(p, 'utf8') || '{}'
return JSON.parse(raw)
} catch (e) {
return {}
}
}
function writeStorage(obj) {
try {
fs.mkdirSync(path.dirname(getStoragePath()), { recursive: true })
fs.writeFileSync(getStoragePath(), JSON.stringify(obj, null, 2), 'utf8')
return true
} catch (e) {
return false
}
}
ipcMain.handle('storage-get', (event, key) => {
const s = readStorage()
if (!key) return s
if (!(key in s)) return null
return { value: s[key] }
})
ipcMain.handle('storage-set', (event, key, value) => {
const s = readStorage()
s[key] = value
writeStorage(s)
return true
})
ipcMain.handle('storage-remove', (event, key) => {
const s = readStorage()
delete s[key]
writeStorage(s)
return true
})
ipcMain.handle('get-api-url', () => {
const s = readStorage()
return normalizeApiUrl(s[API_URL_KEY]) || normalizeApiUrl(DEFAULT_API_URL) || 'http://localhost:4000/api'
})
ipcMain.handle('set-api-url', (event, value) => {
const next = normalizeApiUrl(value)
if (!next) throw new Error('API URL must be a valid http(s) URL')
const s = readStorage()
s[API_URL_KEY] = next
writeStorage(s)
return next
})
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false
}
})
mainWindow.webContents.on('did-fail-load', (_event, code, description, validatedUrl) => {
console.error(`Renderer failed to load (${code}): ${description} @ ${validatedUrl}`)
})
if (isDev) {
mainWindow.loadURL(devServerUrl).catch(err => {
console.error('Failed to load dev server URL, falling back to local dist file.', err)
const rendererEntry = resolveRendererEntry()
mainWindow.loadFile(rendererEntry).catch(loadErr => {
console.error('Fallback renderer load failed.', loadErr)
})
})
mainWindow.webContents.openDevTools()
} else {
const rendererEntry = resolveRendererEntry()
mainWindow.loadFile(rendererEntry).catch(err => {
console.error(`Failed to load renderer entry at ${rendererEntry}`, err)
})
}
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})