'use strict'; const { app, BrowserWindow, Tray, Menu, ipcMain, Notification, nativeImage, shell } = require('electron'); const path = require('path'); // electron-store v10 uses ESM — load dynamically let Store; let store; async function loadStore() { const mod = await import('electron-store'); Store = mod.default; store = new Store({ defaults: { serverUrl: '' }, }); } let mainWindow = null; let tray = null; let setupWindow = null; // ── Helpers ────────────────────────────────────────────────────────────────── function getIconPath() { if (process.platform === 'darwin') return path.join(__dirname, 'assets', 'icon.icns'); if (process.platform === 'win32') return path.join(__dirname, 'assets', 'icon.ico'); return path.join(__dirname, 'assets', 'icon.png'); } function createTray() { const trayImg = nativeImage.createFromPath( path.join(__dirname, 'assets', 'tray.png') ); tray = new Tray(trayImg.isEmpty() ? nativeImage.createEmpty() : trayImg); tray.setToolTip('CloudLink'); const menu = Menu.buildFromTemplate([ { label: 'Open CloudLink', click: () => { mainWindow?.show(); mainWindow?.focus(); } }, { type: 'separator' }, { label: 'Change server URL…', click: () => openSetup(true), }, { type: 'separator' }, { label: 'Quit', click: () => app.quit() }, ]); tray.setContextMenu(menu); tray.on('click', () => { mainWindow?.show(); mainWindow?.focus(); }); } function openSetup(fromTray = false) { if (setupWindow) { setupWindow.focus(); return; } setupWindow = new BrowserWindow({ width: 520, height: 340, resizable: false, title: 'CloudLink — Setup', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, }); const current = store.get('serverUrl'); const html = `

🔗 CloudLink

Enter the URL of your CloudLink server.
For local development use http://localhost:5173

`; setupWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html)); setupWindow.on('closed', () => { setupWindow = null; }); } function createMainWindow(serverUrl) { mainWindow = new BrowserWindow({ width: 1280, height: 820, minWidth: 900, minHeight: 600, title: 'CloudLink', icon: getIconPath(), titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, // Allow the web app to use the Web Crypto API and WebSockets webSecurity: true, }, }); mainWindow.loadURL(serverUrl); // Open external links in the default browser mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); // Hide to tray instead of quitting on close (macOS/Windows convention) mainWindow.on('close', (e) => { if (process.platform !== 'darwin' && !app.isQuitting) { e.preventDefault(); mainWindow.hide(); } }); mainWindow.on('closed', () => { mainWindow = null; }); } // ── IPC ─────────────────────────────────────────────────────────────────────── ipcMain.handle('get-server-url', () => store.get('serverUrl')); ipcMain.handle('set-server-url', (_e, url) => { store.set('serverUrl', url); setupWindow?.close(); if (mainWindow) { mainWindow.loadURL(url); } else { createMainWindow(url); } }); ipcMain.handle('show-notification', (_e, { title, body }) => { if (Notification.isSupported()) { new Notification({ title, body }).show(); } }); // ── App lifecycle ───────────────────────────────────────────────────────────── app.whenReady().then(async () => { await loadStore(); createTray(); const serverUrl = store.get('serverUrl'); if (!serverUrl) { openSetup(); } else { createMainWindow(serverUrl); } app.on('activate', () => { if (!mainWindow && store.get('serverUrl')) { createMainWindow(store.get('serverUrl')); } else { mainWindow?.show(); } }); }); app.on('before-quit', () => { app.isQuitting = true; }); // Keep running when all windows are closed (show via tray) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { // don't quit — user can reopen from tray } });