Update
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('node:path');
|
||||
const { app, BrowserWindow, shell, session, safeStorage } = require('electron');
|
||||
const { ConfigStore } = require('./src/main/config-store.cjs');
|
||||
const { GitService } = require('./src/main/git-service.cjs');
|
||||
const { GiteaService } = require('./src/main/gitea-service.cjs');
|
||||
const { RepositoryService } = require('./src/main/repository-service.cjs');
|
||||
const { DeploymentService } = require('./src/main/deployment-service.cjs');
|
||||
const { RepositoryMonitor } = require('./src/main/repository-monitor.cjs');
|
||||
const { DiagnosticsService } = require('./src/main/diagnostics-service.cjs');
|
||||
const { PreflightService } = require('./src/main/preflight-service.cjs');
|
||||
const { UpdateService } = require('./src/main/update-service.cjs');
|
||||
const { SshService } = require('./src/main/ssh-service.cjs');
|
||||
const { UnraidDeploymentService } = require('./src/main/unraid-deployment-service.cjs');
|
||||
const { registerIpc } = require('./src/main/ipc.cjs');
|
||||
|
||||
let mainWindow;
|
||||
let repositoryMonitor;
|
||||
let operationTimer;
|
||||
let diagnostics;
|
||||
let quitCleanupStarted = false;
|
||||
|
||||
function broadcast(channel, payload) {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (!window.isDestroyed()) window.webContents.send(channel, payload);
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1480,
|
||||
height: 940,
|
||||
minWidth: 1120,
|
||||
minHeight: 720,
|
||||
show: false,
|
||||
backgroundColor: '#0b0e14',
|
||||
title: 'ForgeFlow',
|
||||
icon: path.join(__dirname, 'build', 'icon.png'),
|
||||
autoHideMenuBar: true,
|
||||
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.cjs'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
webSecurity: true,
|
||||
spellcheck: false
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.loadFile(path.join(__dirname, 'src', 'renderer', 'index.html'));
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
diagnostics?.info('window.ready', { size: mainWindow.getSize() });
|
||||
});
|
||||
mainWindow.on('unresponsive', () => diagnostics?.warning('window.unresponsive', {}));
|
||||
mainWindow.webContents.on('render-process-gone', (_event, details) => diagnostics?.error('renderer.process.gone', details));
|
||||
mainWindow.webContents.on('did-fail-load', (_event, code, description, validatedUrl) => diagnostics?.error('renderer.load.failed', { code, description, validatedUrl }));
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (/^https?:\/\//i.test(url)) shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||
if (url !== mainWindow.webContents.getURL()) event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [
|
||||
"default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self'"
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const userDataPath = app.getPath('userData');
|
||||
const store = new ConfigStore(userDataPath);
|
||||
await store.load();
|
||||
diagnostics = new DiagnosticsService({
|
||||
userDataPath,
|
||||
appInfo: { name: app.getName(), version: app.getVersion(), packaged: app.isPackaged },
|
||||
secretProvider: () => [
|
||||
store.getToken(),
|
||||
...(store.data.servers || []).flatMap((server) => {
|
||||
try {
|
||||
const credentials = store.getServerCredentials(server.id);
|
||||
return [credentials.password, credentials.passphrase];
|
||||
} catch { return []; }
|
||||
})
|
||||
],
|
||||
preferencesProvider: () => store.data.preferences
|
||||
});
|
||||
await diagnostics.initialize();
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
diagnostics?.error('process.uncaught-exception', error).finally(() => app.exit(1));
|
||||
});
|
||||
process.on('unhandledRejection', (reason) => diagnostics?.error('process.unhandled-rejection', reason instanceof Error ? reason : { reason }));
|
||||
|
||||
const git = new GitService();
|
||||
const gitea = new GiteaService(store, diagnostics);
|
||||
const repositories = new RepositoryService(store, git, gitea, diagnostics);
|
||||
const deployments = new DeploymentService(store, gitea, git, diagnostics);
|
||||
const ssh = new SshService({ store, diagnostics });
|
||||
const unraid = new UnraidDeploymentService({ store, ssh, git, diagnostics });
|
||||
const updates = new UpdateService({
|
||||
store,
|
||||
gitea,
|
||||
diagnostics,
|
||||
appInfo: { version: app.getVersion(), packaged: app.isPackaged },
|
||||
sourcePath: app.getAppPath(),
|
||||
userDataPath
|
||||
});
|
||||
const preflight = new PreflightService({
|
||||
store,
|
||||
git,
|
||||
gitea,
|
||||
deployments,
|
||||
diagnostics,
|
||||
userDataPath,
|
||||
secureStorageAvailable: () => safeStorage.isEncryptionAvailable()
|
||||
});
|
||||
repositoryMonitor = new RepositoryMonitor({
|
||||
store,
|
||||
git,
|
||||
diagnostics,
|
||||
onChange: (payload) => broadcast('repositories:changed', payload)
|
||||
});
|
||||
repositoryMonitor.restart();
|
||||
registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh, updates, preflight, diagnostics, monitor: repositoryMonitor });
|
||||
createWindow();
|
||||
|
||||
if (store.data.setupComplete && store.data.updates?.autoCheck && store.getToken()) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const status = await updates.check();
|
||||
broadcast('updates:changed', status);
|
||||
} catch (error) {
|
||||
await diagnostics.warning('updates.startup-check.failed', { message: error.message, code: error.code });
|
||||
}
|
||||
}, 2500).unref?.();
|
||||
}
|
||||
|
||||
const gitAvailability = await git.isAvailable();
|
||||
await diagnostics.info('app.ready', {
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
setupComplete: store.data.setupComplete,
|
||||
git: gitAvailability,
|
||||
secureStorageAvailable: safeStorage.isEncryptionAvailable()
|
||||
});
|
||||
|
||||
const scheduleOperationPoll = () => {
|
||||
if (operationTimer) clearTimeout(operationTimer);
|
||||
const intervalMs = Math.max(3, Number(store.data.preferences.operationPollSeconds) || 5) * 1000;
|
||||
operationTimer = setTimeout(async () => {
|
||||
if (store.data.setupComplete && store.getToken()) {
|
||||
const active = store.data.operations.some((item) => item.type === 'deployment' && !['success', 'failed', 'cancelled', 'rolled-back'].includes(item.status));
|
||||
if (active) {
|
||||
const updated = await deployments.refreshActiveOperations().catch(async (error) => {
|
||||
await diagnostics.error('operation-monitor.failed', error);
|
||||
return [];
|
||||
});
|
||||
if (updated.length) broadcast('operations:changed', { operations: updated });
|
||||
}
|
||||
}
|
||||
scheduleOperationPoll();
|
||||
}, intervalMs);
|
||||
operationTimer.unref?.();
|
||||
};
|
||||
scheduleOperationPoll();
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
}).catch(async (error) => {
|
||||
console.error('[startup]', error);
|
||||
await diagnostics?.error('app.startup.failed', error);
|
||||
await diagnostics?.flush();
|
||||
app.exit(1);
|
||||
});
|
||||
|
||||
app.on('before-quit', (event) => {
|
||||
if (quitCleanupStarted) return;
|
||||
event.preventDefault();
|
||||
quitCleanupStarted = true;
|
||||
repositoryMonitor?.stop();
|
||||
if (operationTimer) clearTimeout(operationTimer);
|
||||
Promise.resolve()
|
||||
.then(() => diagnostics?.info('app.quitting', {}))
|
||||
.then(() => diagnostics?.flush())
|
||||
.finally(() => app.quit());
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
Reference in New Issue
Block a user