Files
OctoLauncher/src/main/index.ts
T
OctoWoW fbad749f0c
Build check / build (push) Has been cancelled
Sync launcher: stop phantom update prompt, forum News panel, hardware-aware render distance
Squashed sync from upstream. Highlights:
- Updater no longer reports already-applied deletes as a pending update on
  every launch (guard the del branch on the target still existing)
- Derive the packaged CSP image origin from the configured server URL
- Forum Announcements panel + News tab; hardware-aware farClip recommendation;
  parchment UI; localization and tweak updates
- Addon source refresh; schema and mod-state fixes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:02:46 -07:00

198 lines
5.0 KiB
TypeScript

import { join } from 'path';
import { app, shell, session, BrowserWindow, screen } from 'electron';
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
import { createIPCHandler } from 'electron-trpc/main';
import Logger from 'electron-log/main';
import icon from '~build/icon.png?asset';
import { appRouter } from './api/root';
import Preferences from './modules/preferences';
import Updater from './modules/updater';
import Addons from './modules/addons';
import Mods from './modules/mods';
import { initSelfUpdater } from './modules/selfUpdater';
import {
detectHardware,
recommendFarClip,
HARDWARE_SCHEMA_VERSION
} from './modules/hardware';
Logger.initialize();
Logger.errorHandler.startCatching();
Logger.info('Launcher starting...');
app.disableHardwareAcceleration();
export let mainWindow: BrowserWindow | null = null;
const isOnScreen = (
pos?: {
x: number;
y: number;
width: number;
height: number;
} | null
) => {
if (!pos) return false;
return screen.getAllDisplays().some(d => {
const a = d.workArea;
return (
pos.x < a.x + a.width &&
pos.x + pos.width > a.x &&
pos.y < a.y + a.height &&
pos.y + pos.height > a.y
);
});
};
const createWindow = async () => {
const saved =
Preferences.data.rememberPosition &&
isOnScreen(Preferences.data.windowPosition)
? Preferences.data.windowPosition
: undefined;
const position = saved ?? { width: 1000, height: 700 };
mainWindow = new BrowserWindow({
...position,
minWidth: 1000,
minHeight: 700,
icon,
frame: false,
maximizable: false,
fullscreenable: false,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
contextIsolation: true,
sandbox: false,
devTools: true
}
});
mainWindow.webContents.on('render-process-gone', (_e, details) => {
Logger.error('Renderer process gone:', details);
});
mainWindow.webContents.on('unresponsive', () => {
Logger.error('Renderer unresponsive');
});
mainWindow.webContents.on(
'console-message',
(_e, level, message, line, sourceId) => {
const lvl = level === 3 ? 'error' : level === 2 ? 'warn' : 'info';
Logger[lvl](`[renderer:${lvl}] ${message} (${sourceId}:${line})`);
}
);
mainWindow.webContents.on('before-input-event', (_e, input) => {
if (input.type !== 'keyDown') return;
if (input.key === 'F12') {
mainWindow?.webContents.toggleDevTools();
return;
}
if ((input.control || input.meta) && input.key.toLowerCase() === 'c')
mainWindow?.webContents.copy();
});
createIPCHandler({ router: appRouter, windows: [mainWindow] });
mainWindow.on('ready-to-show', () => {
mainWindow?.show();
});
mainWindow.webContents.setWindowOpenHandler(details => {
shell.openExternal(details.url);
return { action: 'deny' };
});
mainWindow.on('close', () => {
if (!mainWindow) return;
const [x = 0, y = 0] = mainWindow.getPosition();
const [width = 0, height = 0] = mainWindow.getSize();
Preferences.data = { windowPosition: { x, y, width, height } };
});
if (is.dev && process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
};
const gotSingleInstanceLock = is.dev || app.requestSingleInstanceLock();
if (!gotSingleInstanceLock) {
app.quit();
} else {
app.on('second-instance', () => {
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
if (!mainWindow.isVisible()) mainWindow.show();
mainWindow.focus();
});
app.whenReady().then(async () => {
Preferences.data = await Preferences.load();
Addons.verify();
Updater.verify();
Mods.verify();
initSelfUpdater();
void (async () => {
try {
let hardware = Preferences.data.hardware;
if (!hardware || hardware.schemaVersion < HARDWARE_SCHEMA_VERSION) {
hardware = await detectHardware();
Preferences.data = { hardware };
}
const rec = recommendFarClip(hardware ?? null);
if (
Preferences.data.farClipUserSet !== true &&
Preferences.data.config.farClip !== rec
)
Preferences.data = {
config: { ...Preferences.data.config, farClip: rec }
};
} catch (e) {
Logger.error('Hardware detection / farClip recommendation failed', e);
}
})();
electronApp.setAppUserModelId('st.octowow.launcher');
if (app.isPackaged) {
const serverOrigin = new URL(
import.meta.env.MAIN_VITE_SERVER_URL || 'https://octowow.st'
).origin;
session.defaultSession.webRequest.onHeadersReceived((details, cb) => {
cb({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
[
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
`img-src 'self' data: https://octowow.st https://forum.octowow.st ${serverOrigin}`,
"font-src 'self' data:",
"connect-src 'self'"
].join('; ')
]
}
});
});
}
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window);
});
await createWindow();
});
app.on('window-all-closed', () => {
app.quit();
});
}