'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
}
});