🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
884 lines
23 KiB
JavaScript
884 lines
23 KiB
JavaScript
const { app, BrowserWindow, ipcMain, Tray, Menu, session, shell } = require('electron');
|
|
const path = require('path');
|
|
const Store = require('electron-store');
|
|
const axios = require('axios');
|
|
|
|
const store = new Store({
|
|
encryptionKey: 'claude-widget-secure-key-2024'
|
|
});
|
|
|
|
let mainWindow = null;
|
|
let loginWindow = null;
|
|
let silentLoginWindow = null;
|
|
let settingsWindow = null;
|
|
let tray = null;
|
|
let iconGeneratorWindow = null;
|
|
|
|
// Tray icon state
|
|
let cachedUsageData = null;
|
|
let trayUpdateInterval = null;
|
|
let lastTrayUpdate = 0;
|
|
|
|
// Window configuration
|
|
const WIDGET_WIDTH = 480;
|
|
const WIDGET_HEIGHT = 140;
|
|
const SETTINGS_WIDTH = 500;
|
|
const SETTINGS_HEIGHT = 680;
|
|
|
|
function createMainWindow() {
|
|
// Load saved position or use defaults
|
|
const savedPosition = store.get('windowPosition');
|
|
const windowOptions = {
|
|
width: WIDGET_WIDTH,
|
|
height: WIDGET_HEIGHT,
|
|
minWidth: 320,
|
|
maxWidth: 600,
|
|
minHeight: 96,
|
|
maxHeight: 180,
|
|
frame: false,
|
|
transparent: true,
|
|
alwaysOnTop: true,
|
|
resizable: true,
|
|
skipTaskbar: false,
|
|
icon: path.join(__dirname, 'assets/icon.ico'),
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
preload: path.join(__dirname, 'preload.js')
|
|
}
|
|
};
|
|
|
|
// Apply saved position if it exists
|
|
if (savedPosition) {
|
|
windowOptions.x = savedPosition.x;
|
|
windowOptions.y = savedPosition.y;
|
|
}
|
|
|
|
mainWindow = new BrowserWindow(windowOptions);
|
|
|
|
mainWindow.loadFile('src/renderer/index.html');
|
|
|
|
// Make window draggable
|
|
mainWindow.setAlwaysOnTop(true, 'floating');
|
|
mainWindow.setVisibleOnAllWorkspaces(true);
|
|
|
|
// Save position when window is moved
|
|
mainWindow.on('move', () => {
|
|
const position = mainWindow.getBounds();
|
|
store.set('windowPosition', { x: position.x, y: position.y });
|
|
});
|
|
|
|
mainWindow.on('closed', () => {
|
|
mainWindow = null;
|
|
});
|
|
|
|
// Development tools
|
|
if (process.env.NODE_ENV === 'development') {
|
|
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|
}
|
|
}
|
|
|
|
function createLoginWindow() {
|
|
loginWindow = new BrowserWindow({
|
|
width: 800,
|
|
height: 700,
|
|
parent: mainWindow,
|
|
modal: true,
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true
|
|
}
|
|
});
|
|
|
|
loginWindow.loadURL('https://claude.ai');
|
|
|
|
let loginCheckInterval = null;
|
|
let hasLoggedIn = false;
|
|
|
|
// Function to check login status
|
|
async function checkLoginStatus() {
|
|
if (hasLoggedIn || !loginWindow) return;
|
|
|
|
try {
|
|
const cookies = await session.defaultSession.cookies.get({
|
|
url: 'https://claude.ai',
|
|
name: 'sessionKey'
|
|
});
|
|
|
|
if (cookies.length > 0) {
|
|
const sessionKey = cookies[0].value;
|
|
console.log('Session key found, attempting to get org ID...');
|
|
|
|
// Fetch org ID from API
|
|
let orgId = null;
|
|
try {
|
|
const response = await axios.get('https://claude.ai/api/organizations', {
|
|
headers: {
|
|
'Cookie': `sessionKey=${sessionKey}`,
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
}
|
|
});
|
|
|
|
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
|
orgId = response.data[0].uuid || response.data[0].id;
|
|
console.log('Org ID fetched from API:', orgId);
|
|
}
|
|
} catch (err) {
|
|
console.log('API not ready yet:', err.message);
|
|
}
|
|
|
|
if (sessionKey && orgId) {
|
|
hasLoggedIn = true;
|
|
if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
|
|
console.log('Sending login-success to main window...');
|
|
store.set('sessionKey', sessionKey);
|
|
store.set('organizationId', orgId);
|
|
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send('login-success', { sessionKey, organizationId: orgId });
|
|
console.log('login-success sent');
|
|
} else {
|
|
console.error('mainWindow is null, cannot send login-success');
|
|
}
|
|
|
|
loginWindow.close();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in login check:', error);
|
|
}
|
|
}
|
|
|
|
// Check on page load
|
|
loginWindow.webContents.on('did-finish-load', async () => {
|
|
const url = loginWindow.webContents.getURL();
|
|
console.log('Login page loaded:', url);
|
|
|
|
if (url.includes('claude.ai')) {
|
|
await checkLoginStatus();
|
|
}
|
|
});
|
|
|
|
// Also check on navigation (URL changes)
|
|
loginWindow.webContents.on('did-navigate', async (event, url) => {
|
|
console.log('Navigated to:', url);
|
|
if (url.includes('claude.ai')) {
|
|
await checkLoginStatus();
|
|
}
|
|
});
|
|
|
|
// Poll periodically in case the session becomes ready without a page navigation
|
|
loginCheckInterval = setInterval(async () => {
|
|
if (!hasLoggedIn && loginWindow) {
|
|
await checkLoginStatus();
|
|
} else if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
}, 2000);
|
|
|
|
loginWindow.on('closed', () => {
|
|
if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
loginWindow = null;
|
|
});
|
|
}
|
|
|
|
// Attempt silent login in a hidden browser window
|
|
async function attemptSilentLogin() {
|
|
console.log('[Main] Attempting silent login...');
|
|
|
|
// Notify renderer that we're trying to auto-login
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send('silent-login-started');
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
silentLoginWindow = new BrowserWindow({
|
|
width: 800,
|
|
height: 700,
|
|
show: false, // Hidden window
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true
|
|
}
|
|
});
|
|
|
|
silentLoginWindow.loadURL('https://claude.ai');
|
|
|
|
let loginCheckInterval = null;
|
|
let hasLoggedIn = false;
|
|
const SILENT_LOGIN_TIMEOUT = 15000; // 15 seconds timeout
|
|
|
|
// Function to check login status
|
|
async function checkLoginStatus() {
|
|
if (hasLoggedIn || !silentLoginWindow) return;
|
|
|
|
try {
|
|
const cookies = await session.defaultSession.cookies.get({
|
|
url: 'https://claude.ai',
|
|
name: 'sessionKey'
|
|
});
|
|
|
|
if (cookies.length > 0) {
|
|
const sessionKey = cookies[0].value;
|
|
console.log('[Main] Silent login: Session key found, attempting to get org ID...');
|
|
|
|
// Fetch org ID from API
|
|
let orgId = null;
|
|
try {
|
|
const response = await axios.get('https://claude.ai/api/organizations', {
|
|
headers: {
|
|
'Cookie': `sessionKey=${sessionKey}`,
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
}
|
|
});
|
|
|
|
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
|
orgId = response.data[0].uuid || response.data[0].id;
|
|
console.log('[Main] Silent login: Org ID fetched from API:', orgId);
|
|
}
|
|
} catch (err) {
|
|
console.log('[Main] Silent login: API not ready yet:', err.message);
|
|
}
|
|
|
|
if (sessionKey && orgId) {
|
|
hasLoggedIn = true;
|
|
if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
|
|
console.log('[Main] Silent login successful!');
|
|
store.set('sessionKey', sessionKey);
|
|
store.set('organizationId', orgId);
|
|
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send('login-success', { sessionKey, organizationId: orgId });
|
|
}
|
|
|
|
silentLoginWindow.close();
|
|
resolve(true);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('[Main] Silent login check error:', error);
|
|
}
|
|
}
|
|
|
|
// Check on page load
|
|
silentLoginWindow.webContents.on('did-finish-load', async () => {
|
|
const url = silentLoginWindow.webContents.getURL();
|
|
console.log('[Main] Silent login page loaded:', url);
|
|
|
|
if (url.includes('claude.ai')) {
|
|
await checkLoginStatus();
|
|
}
|
|
});
|
|
|
|
// Also check on navigation
|
|
silentLoginWindow.webContents.on('did-navigate', async (event, url) => {
|
|
console.log('[Main] Silent login navigated to:', url);
|
|
if (url.includes('claude.ai')) {
|
|
await checkLoginStatus();
|
|
}
|
|
});
|
|
|
|
// Poll periodically
|
|
loginCheckInterval = setInterval(async () => {
|
|
if (!hasLoggedIn && silentLoginWindow) {
|
|
await checkLoginStatus();
|
|
} else if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
}, 1000);
|
|
|
|
// Timeout - if silent login doesn't work, fall back to visible login
|
|
setTimeout(() => {
|
|
if (!hasLoggedIn) {
|
|
console.log('[Main] Silent login timeout, falling back to visible login...');
|
|
if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
if (silentLoginWindow) {
|
|
silentLoginWindow.close();
|
|
}
|
|
|
|
// Notify renderer that silent login failed
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send('silent-login-failed');
|
|
}
|
|
|
|
// Open visible login window
|
|
createLoginWindow();
|
|
resolve(false);
|
|
}
|
|
}, SILENT_LOGIN_TIMEOUT);
|
|
|
|
silentLoginWindow.on('closed', () => {
|
|
if (loginCheckInterval) {
|
|
clearInterval(loginCheckInterval);
|
|
loginCheckInterval = null;
|
|
}
|
|
silentLoginWindow = null;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Create hidden window for generating tray icons
|
|
function createIconGeneratorWindow() {
|
|
if (iconGeneratorWindow) return;
|
|
|
|
iconGeneratorWindow = new BrowserWindow({
|
|
width: 100,
|
|
height: 100,
|
|
show: false,
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
contextIsolation: false
|
|
}
|
|
});
|
|
|
|
iconGeneratorWindow.loadFile(path.join(__dirname, 'src/icon-generator.html'));
|
|
|
|
iconGeneratorWindow.on('closed', () => {
|
|
iconGeneratorWindow = null;
|
|
});
|
|
}
|
|
|
|
// Generate tray icon with circular progress indicator
|
|
async function generateTrayIcon(percentage, colors, showText = true) {
|
|
const { nativeImage } = require('electron');
|
|
|
|
if (!iconGeneratorWindow) {
|
|
createIconGeneratorWindow();
|
|
// Wait for window to load
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
ipcMain.once('icon-generated', (event, dataUrl) => {
|
|
resolve(nativeImage.createFromDataURL(dataUrl));
|
|
});
|
|
|
|
iconGeneratorWindow.webContents.send('generate-icon', { percentage, colors, showText });
|
|
});
|
|
}
|
|
|
|
// Update tray icon with current usage data
|
|
async function updateTrayIcon() {
|
|
if (!tray) return;
|
|
|
|
// If no data yet, show default icon
|
|
if (!cachedUsageData) {
|
|
tray.setImage(path.join(__dirname, 'assets/tray-icon.png'));
|
|
tray.setToolTip('Claude Usage Widget - Loading...');
|
|
return;
|
|
}
|
|
|
|
// Get tray settings from store
|
|
const traySettings = store.get('traySettings', {
|
|
displayMode: 'session',
|
|
showText: false,
|
|
colors: {
|
|
normal: { start: '#8b5cf6', end: '#a78bfa' },
|
|
warning: { start: '#f59e0b', end: '#fbbf24' },
|
|
danger: { start: '#ef4444', end: '#f87171' }
|
|
}
|
|
});
|
|
|
|
// Get appropriate usage data based on display mode
|
|
let percentage, resetsAt;
|
|
if (traySettings.displayMode === 'weekly') {
|
|
percentage = cachedUsageData.seven_day?.utilization || 0;
|
|
resetsAt = cachedUsageData.seven_day?.resets_at;
|
|
} else {
|
|
percentage = cachedUsageData.five_hour?.utilization || 0;
|
|
resetsAt = cachedUsageData.five_hour?.resets_at;
|
|
}
|
|
|
|
// Generate and set new icon
|
|
try {
|
|
const icon = await generateTrayIcon(percentage, traySettings.colors, traySettings.showText);
|
|
tray.setImage(icon);
|
|
|
|
// Update tooltip with detailed info
|
|
const modeLabel = traySettings.displayMode === 'weekly' ? '7-day' : '5-hour';
|
|
const resetTime = resetsAt ? new Date(resetsAt).toLocaleString() : 'N/A';
|
|
tray.setToolTip(
|
|
`Claude Usage: ${Math.round(percentage)}% (${modeLabel})\nResets: ${resetTime}`
|
|
);
|
|
} catch (error) {
|
|
console.error('Failed to generate tray icon:', error);
|
|
}
|
|
}
|
|
|
|
// Throttled tray update to avoid excessive updates
|
|
function updateTrayIconThrottled() {
|
|
const now = Date.now();
|
|
const interval = store.get('trayUpdateInterval', 30) * 1000; // Convert to ms
|
|
|
|
if (now - lastTrayUpdate >= interval) {
|
|
updateTrayIcon();
|
|
lastTrayUpdate = now;
|
|
}
|
|
}
|
|
|
|
// Start periodic tray icon updates
|
|
function startTrayUpdateTimer() {
|
|
if (trayUpdateInterval) clearInterval(trayUpdateInterval);
|
|
|
|
const interval = store.get('trayUpdateInterval', 30) * 1000;
|
|
trayUpdateInterval = setInterval(() => {
|
|
if (cachedUsageData) {
|
|
updateTrayIcon();
|
|
}
|
|
}, interval);
|
|
}
|
|
|
|
function createTray() {
|
|
try {
|
|
tray = new Tray(path.join(__dirname, 'assets/tray-icon.png'));
|
|
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
{
|
|
label: 'Show Widget',
|
|
click: () => {
|
|
if (mainWindow) {
|
|
mainWindow.show();
|
|
} else {
|
|
createMainWindow();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
label: 'Refresh',
|
|
click: () => {
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send('refresh-usage');
|
|
}
|
|
}
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Settings',
|
|
click: () => {
|
|
createSettingsWindow();
|
|
}
|
|
},
|
|
{
|
|
label: 'Re-login',
|
|
click: () => {
|
|
store.delete('sessionKey');
|
|
store.delete('organizationId');
|
|
createLoginWindow();
|
|
}
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Exit',
|
|
click: () => {
|
|
app.quit();
|
|
}
|
|
}
|
|
]);
|
|
|
|
tray.setToolTip('Claude Usage Widget');
|
|
tray.setContextMenu(contextMenu);
|
|
|
|
tray.on('click', () => {
|
|
if (mainWindow) {
|
|
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to create tray:', error);
|
|
}
|
|
}
|
|
|
|
function createSettingsWindow() {
|
|
if (settingsWindow) {
|
|
settingsWindow.focus();
|
|
return;
|
|
}
|
|
|
|
settingsWindow = new BrowserWindow({
|
|
width: SETTINGS_WIDTH,
|
|
height: SETTINGS_HEIGHT,
|
|
title: 'Settings - Claude Usage Widget',
|
|
frame: false,
|
|
resizable: true,
|
|
minimizable: true,
|
|
maximizable: false,
|
|
icon: path.join(__dirname, 'assets/icon.ico'),
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
preload: path.join(__dirname, 'preload.js')
|
|
}
|
|
});
|
|
|
|
settingsWindow.loadFile('src/renderer/settings.html');
|
|
|
|
settingsWindow.on('closed', () => {
|
|
settingsWindow = null;
|
|
});
|
|
|
|
// Development tools
|
|
if (process.env.NODE_ENV === 'development') {
|
|
settingsWindow.webContents.openDevTools({ mode: 'detach' });
|
|
}
|
|
}
|
|
|
|
// IPC Handlers
|
|
ipcMain.handle('get-credentials', () => {
|
|
return {
|
|
sessionKey: store.get('sessionKey'),
|
|
organizationId: store.get('organizationId')
|
|
};
|
|
});
|
|
|
|
ipcMain.handle('save-credentials', (event, { sessionKey, organizationId }) => {
|
|
store.set('sessionKey', sessionKey);
|
|
if (organizationId) {
|
|
store.set('organizationId', organizationId);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
ipcMain.handle('delete-credentials', async () => {
|
|
store.delete('sessionKey');
|
|
store.delete('organizationId');
|
|
|
|
// Clear the session cookie to ensure actual logout
|
|
try {
|
|
await session.defaultSession.cookies.remove('https://claude.ai', 'sessionKey');
|
|
// Also try checking for other auth cookies or clear storage if needed
|
|
// await session.defaultSession.clearStorageData({ storages: ['cookies'] });
|
|
} catch (error) {
|
|
console.error('Failed to clear cookies:', error);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
ipcMain.on('open-login', () => {
|
|
createLoginWindow();
|
|
});
|
|
|
|
ipcMain.on('open-settings', () => {
|
|
createSettingsWindow();
|
|
});
|
|
|
|
ipcMain.on('minimize-window', () => {
|
|
if (mainWindow) mainWindow.hide();
|
|
});
|
|
|
|
ipcMain.on('close-window', () => {
|
|
const appSettings = store.get('appSettings', { closeToTray: false });
|
|
if (appSettings.closeToTray) {
|
|
mainWindow.hide();
|
|
} else {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('get-window-position', () => {
|
|
if (mainWindow) {
|
|
return mainWindow.getBounds();
|
|
}
|
|
return null;
|
|
});
|
|
|
|
ipcMain.handle('set-window-position', (event, { x, y }) => {
|
|
if (mainWindow) {
|
|
mainWindow.setPosition(x, y);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
ipcMain.on('set-window-height', (event, height) => {
|
|
if (mainWindow) {
|
|
const currentBounds = mainWindow.getBounds();
|
|
mainWindow.setSize(currentBounds.width, Math.round(height));
|
|
}
|
|
});
|
|
|
|
ipcMain.on('set-window-size', (event, { width, height }) => {
|
|
if (mainWindow) {
|
|
mainWindow.setSize(Math.round(width), Math.round(height));
|
|
}
|
|
});
|
|
|
|
ipcMain.on('open-external', (event, url) => {
|
|
shell.openExternal(url);
|
|
});
|
|
|
|
// Color preferences handlers
|
|
const DEFAULT_COLOR_PREFERENCES = {
|
|
normal: { start: '#8b5cf6', end: '#a78bfa' },
|
|
warning: { start: '#f59e0b', end: '#fbbf24' },
|
|
danger: { start: '#ef4444', end: '#f87171' }
|
|
};
|
|
|
|
ipcMain.handle('get-color-preferences', () => {
|
|
const saved = store.get('colorPreferences');
|
|
return saved || DEFAULT_COLOR_PREFERENCES;
|
|
});
|
|
|
|
ipcMain.handle('set-color-preferences', (event, preferences) => {
|
|
store.set('colorPreferences', preferences);
|
|
return true;
|
|
});
|
|
|
|
ipcMain.handle('notify-color-change', (event, preferences) => {
|
|
// Notify main window to update colors
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('colors-changed', preferences);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
ipcMain.handle('fetch-usage-data', async () => {
|
|
console.log('[Main] fetch-usage-data handler called');
|
|
const sessionKey = store.get('sessionKey');
|
|
const organizationId = store.get('organizationId');
|
|
|
|
console.log('[Main] Credentials:', {
|
|
hasSessionKey: !!sessionKey,
|
|
organizationId
|
|
});
|
|
|
|
if (!sessionKey || !organizationId) {
|
|
throw new Error('Missing credentials');
|
|
}
|
|
|
|
try {
|
|
console.log('[Main] Making API request to:', `https://claude.ai/api/organizations/${organizationId}/usage`);
|
|
const response = await axios.get(
|
|
`https://claude.ai/api/organizations/${organizationId}/usage`,
|
|
{
|
|
headers: {
|
|
'Cookie': `sessionKey=${sessionKey}`,
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
}
|
|
}
|
|
);
|
|
console.log('[Main] API request successful, status:', response.status);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('[Main] API request failed:', error.message);
|
|
if (error.response) {
|
|
console.error('[Main] Response status:', error.response.status);
|
|
if (error.response.status === 401 || error.response.status === 403) {
|
|
// Session expired - attempt silent re-login
|
|
console.log('[Main] Session expired, attempting silent re-login...');
|
|
store.delete('sessionKey');
|
|
store.delete('organizationId');
|
|
|
|
// Don't clear cookies - we need them for silent login to work with OAuth
|
|
// The silent login will use existing Google/OAuth session if available
|
|
|
|
// Attempt silent login (will notify renderer appropriately)
|
|
attemptSilentLogin();
|
|
|
|
throw new Error('SessionExpired');
|
|
}
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
// Tray icon IPC handlers
|
|
ipcMain.on('usage-data-update', (event, data) => {
|
|
cachedUsageData = data;
|
|
updateTrayIconThrottled();
|
|
});
|
|
|
|
ipcMain.handle('get-tray-settings', () => {
|
|
return store.get('traySettings', {
|
|
displayMode: 'session',
|
|
showText: false,
|
|
colors: {
|
|
normal: { start: '#8b5cf6', end: '#a78bfa' },
|
|
warning: { start: '#f59e0b', end: '#fbbf24' },
|
|
danger: { start: '#ef4444', end: '#f87171' }
|
|
}
|
|
});
|
|
});
|
|
|
|
ipcMain.on('set-tray-settings', (event, settings) => {
|
|
store.set('traySettings', settings);
|
|
updateTrayIcon(); // Immediate update
|
|
});
|
|
|
|
ipcMain.handle('get-tray-update-interval', () => {
|
|
return store.get('trayUpdateInterval', 30);
|
|
});
|
|
|
|
ipcMain.on('set-tray-update-interval', (event, seconds) => {
|
|
store.set('trayUpdateInterval', seconds);
|
|
startTrayUpdateTimer(); // Restart timer with new interval
|
|
});
|
|
|
|
// Theme settings IPC handlers
|
|
ipcMain.handle('get-theme-settings', () => {
|
|
return store.get('themeSettings', {
|
|
backgroundStart: '#1e1e2e',
|
|
backgroundEnd: '#2a2a3e',
|
|
textPrimary: '#e0e0e0',
|
|
textSecondary: '#a0a0a0',
|
|
titleBarBg: '#000000',
|
|
titleBarOpacity: 30,
|
|
borderColor: '#ffffff',
|
|
borderOpacity: 10
|
|
});
|
|
});
|
|
|
|
ipcMain.handle('set-theme-settings', (event, theme) => {
|
|
store.set('themeSettings', theme);
|
|
return true;
|
|
});
|
|
|
|
ipcMain.handle('notify-theme-change', (event, theme) => {
|
|
// Notify main window to update theme
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('theme-changed', theme);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// App settings
|
|
ipcMain.handle('get-app-settings', () => {
|
|
return store.get('appSettings', {
|
|
startOnBoot: false,
|
|
startMinimized: false,
|
|
closeToTray: false,
|
|
uiUpdateInterval: 30 // 30 seconds
|
|
});
|
|
});
|
|
|
|
ipcMain.handle('set-app-settings', (event, settings) => {
|
|
const currentSettings = store.get('appSettings', {});
|
|
const newSettings = { ...currentSettings, ...settings };
|
|
store.set('appSettings', newSettings);
|
|
|
|
// Apply auto-launch setting
|
|
if (settings.hasOwnProperty('startOnBoot')) {
|
|
const isDev = !app.isPackaged;
|
|
app.setLoginItemSettings({
|
|
openAtLogin: settings.startOnBoot,
|
|
path: process.execPath,
|
|
args: isDev ? [app.getAppPath()] : []
|
|
});
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// UI visibility settings
|
|
ipcMain.handle('get-ui-visibility', () => {
|
|
return store.get('uiVisibility', {
|
|
showSessionSection: true,
|
|
showWeeklySection: true,
|
|
sessionShowLabel: true,
|
|
sessionShowBar: true,
|
|
sessionShowPercentage: true,
|
|
sessionShowCircle: true,
|
|
sessionShowTime: true,
|
|
weeklyShowLabel: true,
|
|
weeklyShowBar: true,
|
|
weeklyShowPercentage: true,
|
|
weeklyShowCircle: true,
|
|
weeklyShowTime: true
|
|
});
|
|
});
|
|
|
|
ipcMain.handle('set-ui-visibility', (event, visibility) => {
|
|
store.set('uiVisibility', visibility);
|
|
// Notify main window to update visibility
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('ui-visibility-changed', visibility);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// App lifecycle
|
|
app.whenReady().then(() => {
|
|
// Apply startup settings
|
|
const appSettings = store.get('appSettings', {
|
|
startOnBoot: false,
|
|
startMinimized: false,
|
|
closeToTray: false
|
|
});
|
|
|
|
// Set auto-launch
|
|
const isDev = !app.isPackaged;
|
|
app.setLoginItemSettings({
|
|
openAtLogin: appSettings.startOnBoot,
|
|
path: process.execPath,
|
|
args: isDev ? [app.getAppPath()] : []
|
|
});
|
|
|
|
createMainWindow();
|
|
createTray();
|
|
createIconGeneratorWindow();
|
|
startTrayUpdateTimer();
|
|
|
|
// Start minimized if enabled
|
|
if (appSettings.startMinimized) {
|
|
mainWindow.hide();
|
|
}
|
|
|
|
// Check if we have credentials
|
|
// const hasCredentials = store.get('sessionKey') && store.get('organizationId');
|
|
// if (!hasCredentials) {
|
|
// setTimeout(() => {
|
|
// createLoginWindow();
|
|
// }, 1000);
|
|
// }
|
|
});
|
|
|
|
app.on('before-quit', () => {
|
|
if (trayUpdateInterval) {
|
|
clearInterval(trayUpdateInterval);
|
|
}
|
|
if (iconGeneratorWindow && !iconGeneratorWindow.isDestroyed()) {
|
|
iconGeneratorWindow.destroy();
|
|
}
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
// Don't quit on macOS
|
|
if (process.platform !== 'darwin') {
|
|
// Keep running in tray
|
|
}
|
|
});
|
|
|
|
app.on('activate', () => {
|
|
if (mainWindow === null) {
|
|
createMainWindow();
|
|
}
|
|
});
|
|
|
|
// Prevent multiple instances
|
|
const gotTheLock = app.requestSingleInstanceLock();
|
|
if (!gotTheLock) {
|
|
app.quit();
|
|
} else {
|
|
app.on('second-instance', () => {
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
mainWindow.focus();
|
|
}
|
|
});
|
|
}
|