2026-03-20 19:50:50 +02:00
|
|
|
import { app, BrowserWindow, shell, session } from 'electron';
|
|
|
|
|
import path from 'path';
|
2026-04-24 19:14:37 +02:00
|
|
|
import { trackEvent } from './analytics';
|
2026-03-20 19:50:50 +02:00
|
|
|
import { showErrorDialog } from './errors';
|
|
|
|
|
import { getServerPort } from './server';
|
2026-04-07 04:45:48 -07:00
|
|
|
import { setActiveSearchSpaceId } from './active-search-space';
|
2026-03-20 19:50:50 +02:00
|
|
|
|
|
|
|
|
const isDev = !app.isPackaged;
|
|
|
|
|
const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string;
|
|
|
|
|
|
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
2026-04-20 12:42:06 -07:00
|
|
|
let isQuitting = false;
|
2026-03-20 19:50:50 +02:00
|
|
|
|
|
|
|
|
export function getMainWindow(): BrowserWindow | null {
|
|
|
|
|
return mainWindow;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 12:42:06 -07:00
|
|
|
// Called from main.ts on `before-quit` so the close-to-tray handler knows
|
|
|
|
|
// to actually let the window die instead of hiding it.
|
|
|
|
|
export function markQuitting(): void {
|
|
|
|
|
isQuitting = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 13:44:57 +02:00
|
|
|
export function createMainWindow(initialPath = '/dashboard'): BrowserWindow {
|
2026-03-20 19:50:50 +02:00
|
|
|
mainWindow = new BrowserWindow({
|
|
|
|
|
width: 1280,
|
|
|
|
|
height: 800,
|
|
|
|
|
minWidth: 800,
|
|
|
|
|
minHeight: 600,
|
|
|
|
|
webPreferences: {
|
|
|
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
|
|
|
contextIsolation: true,
|
|
|
|
|
nodeIntegration: false,
|
|
|
|
|
sandbox: true,
|
|
|
|
|
webviewTag: false,
|
|
|
|
|
},
|
|
|
|
|
show: false,
|
|
|
|
|
titleBarStyle: 'hiddenInset',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
|
|
|
mainWindow?.show();
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-02 13:44:57 +02:00
|
|
|
mainWindow.loadURL(`http://localhost:${getServerPort()}${initialPath}`);
|
2026-03-20 19:50:50 +02:00
|
|
|
|
|
|
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
|
|
|
if (url.startsWith('http://localhost')) {
|
|
|
|
|
return { action: 'allow' };
|
|
|
|
|
}
|
|
|
|
|
shell.openExternal(url);
|
|
|
|
|
return { action: 'deny' };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] };
|
|
|
|
|
session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
|
|
|
|
|
const rewritten = details.url.replace(HOSTED_FRONTEND_URL, `http://localhost:${getServerPort()}`);
|
|
|
|
|
callback({ redirectURL: rewritten });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => {
|
|
|
|
|
console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`);
|
|
|
|
|
if (errorCode === -3) return;
|
|
|
|
|
showErrorDialog('Page failed to load', new Error(`${errorDescription} (${errorCode})\n${validatedURL}`));
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-07 04:45:48 -07:00
|
|
|
// Auto-sync active search space from URL navigation
|
|
|
|
|
const syncSearchSpace = (url: string) => {
|
|
|
|
|
const match = url.match(/\/dashboard\/(\d+)/);
|
|
|
|
|
if (match) {
|
|
|
|
|
setActiveSearchSpaceId(match[1]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
mainWindow.webContents.on('did-navigate', (_event, url) => syncSearchSpace(url));
|
|
|
|
|
mainWindow.webContents.on('did-navigate-in-page', (_event, url) => syncSearchSpace(url));
|
|
|
|
|
|
2026-03-20 19:50:50 +02:00
|
|
|
if (isDev) {
|
|
|
|
|
mainWindow.webContents.openDevTools();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 12:42:06 -07:00
|
|
|
// Hide-to-tray on close (don't actually destroy the window unless the
|
|
|
|
|
// user really is quitting). Applies to every instance — including the one
|
|
|
|
|
// created lazily after a launch-at-login boot.
|
|
|
|
|
mainWindow.on('close', (e) => {
|
|
|
|
|
if (!isQuitting && mainWindow) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
mainWindow.hide();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-20 19:50:50 +02:00
|
|
|
mainWindow.on('closed', () => {
|
|
|
|
|
mainWindow = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return mainWindow;
|
|
|
|
|
}
|
2026-04-24 19:14:37 +02:00
|
|
|
|
|
|
|
|
export function showMainWindow(source: 'tray_click' | 'tray_menu' | 'shortcut' = 'tray_click'): void {
|
|
|
|
|
const existing = getMainWindow();
|
|
|
|
|
const reopened = !existing || existing.isDestroyed();
|
|
|
|
|
if (reopened) {
|
|
|
|
|
createMainWindow('/dashboard');
|
|
|
|
|
} else {
|
|
|
|
|
existing.show();
|
|
|
|
|
existing.focus();
|
|
|
|
|
}
|
|
|
|
|
trackEvent('desktop_main_window_shown', { source, reopened });
|
|
|
|
|
}
|