Features: - Welcome screen on first run (provider choice before LS starts) - 15+ AI providers (Google Gemini, OpenAI, Anthropic, DeepSeek, Ollama, etc.) - Provider config syncs to endpoints.json for translation proxy - Built-in Node.js translation proxy for non-native backends - Auto-update support, tray integration, URI scheme handler
242 lines
9.7 KiB
JavaScript
242 lines
9.7 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.updateActions = exports.MenuUpdateStep = void 0;
|
|
exports.broadcastState = broadcastState;
|
|
exports.initAutoUpdater = initAutoUpdater;
|
|
exports.checkForUpdates = checkForUpdates;
|
|
exports.quitAndInstall = quitAndInstall;
|
|
const electron_updater_1 = require("electron-updater");
|
|
const electron_1 = require("electron");
|
|
const path = __importStar(require("path"));
|
|
const child_process_1 = require("child_process");
|
|
var MenuUpdateStep;
|
|
(function (MenuUpdateStep) {
|
|
MenuUpdateStep["CheckForUpdates"] = "Check for Updates";
|
|
MenuUpdateStep["CheckingForUpdates"] = "Checking for Updates...";
|
|
MenuUpdateStep["DownloadingUpdate"] = "Downloading Update...";
|
|
MenuUpdateStep["RestartToUpdate"] = "Restart to Update";
|
|
})(MenuUpdateStep || (exports.MenuUpdateStep = MenuUpdateStep = {}));
|
|
exports.updateActions = {
|
|
[MenuUpdateStep.CheckForUpdates]: () => checkForUpdates(true),
|
|
[MenuUpdateStep.CheckingForUpdates]: undefined,
|
|
[MenuUpdateStep.DownloadingUpdate]: undefined,
|
|
[MenuUpdateStep.RestartToUpdate]: () => quitAndInstall(),
|
|
};
|
|
// True if the last call to check for updates was from a user click in the menu.
|
|
let isManualCheck = false;
|
|
// How long to wait after app start before first update check (ms)
|
|
const INITIAL_CHECK_DELAY_MS = 10000; // 10 seconds
|
|
// How often to re-check for updates after the initial check (ms)
|
|
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
/** Broadcast a state change to every open BrowserWindow. */
|
|
function broadcastState(state) {
|
|
for (const win of electron_1.BrowserWindow.getAllWindows()) {
|
|
win.webContents.send('updater:state-changed', state);
|
|
}
|
|
}
|
|
/**
|
|
* Updates the state of the menu item based on the current step of the updater.
|
|
*/
|
|
function updateMenuState(step) {
|
|
const menu = electron_1.Menu.getApplicationMenu();
|
|
if (menu) {
|
|
const item = menu.getMenuItemById('check-for-updates');
|
|
if (item) {
|
|
item.label = step;
|
|
item.enabled = exports.updateActions[step] !== undefined;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Initializes the auto-updater and registers IPC handlers.
|
|
* Call once after the first window is created.
|
|
*
|
|
* The updater will:
|
|
* 1. Wait INITIAL_CHECK_DELAY_MS ms, then check for updates.
|
|
* 2. Re-check every CHECK_INTERVAL_MS ms.
|
|
* 3. Download updates automatically in the background.
|
|
* 4. Broadcast state to the renderer so AppUpdateButton can display progress.
|
|
*/
|
|
function initAutoUpdater(isHeadless) {
|
|
// In dev mode (npm start), electron-updater skips checks because the app
|
|
// isn't packaged. Force it to use the dev config file instead.
|
|
if (!electron_1.app.isPackaged) {
|
|
electron_updater_1.autoUpdater.forceDevUpdateConfig = true;
|
|
electron_updater_1.autoUpdater.updateConfigPath = path.join(electron_1.app.getAppPath(), 'dev-app-update.yml');
|
|
}
|
|
// Set the channel based on architecture and OS.
|
|
// On Windows, we need to explicitly append '-win' to match the artifact name.
|
|
// On macOS and linux, Electron automatically appends the OS to the channel name.
|
|
if (process.platform === 'win32') {
|
|
electron_updater_1.autoUpdater.channel = `latest-${process.arch}-win`;
|
|
}
|
|
else {
|
|
electron_updater_1.autoUpdater.channel = `latest-${process.arch}`;
|
|
}
|
|
electron_updater_1.autoUpdater.autoDownload = true;
|
|
electron_updater_1.autoUpdater.autoInstallOnAppQuit = electron_1.app.isPackaged;
|
|
// Auto-updater event handlers → broadcast to renderer
|
|
electron_updater_1.autoUpdater.on('checking-for-update', () => {
|
|
console.log('[AutoUpdater] Checking for update…');
|
|
broadcastState({ type: 'checking for updates' });
|
|
updateMenuState(MenuUpdateStep.CheckingForUpdates);
|
|
});
|
|
electron_updater_1.autoUpdater.on('update-available', (info) => {
|
|
console.log(`[AutoUpdater] Update available: ${info.version}`);
|
|
broadcastState({
|
|
type: 'available for download',
|
|
update: { version: info.version },
|
|
});
|
|
updateMenuState(MenuUpdateStep.DownloadingUpdate);
|
|
isManualCheck = false;
|
|
});
|
|
electron_updater_1.autoUpdater.on('update-not-available', (info) => {
|
|
console.log(`[AutoUpdater] Up to date (${info.version})`);
|
|
broadcastState({ type: 'idle' });
|
|
updateMenuState(MenuUpdateStep.CheckForUpdates);
|
|
if (isManualCheck && !isHeadless) {
|
|
const win = electron_1.BrowserWindow.getFocusedWindow();
|
|
const options = {
|
|
type: 'info',
|
|
title: 'Check for Updates',
|
|
message: 'No updates available',
|
|
buttons: ['OK'],
|
|
};
|
|
if (win) {
|
|
electron_1.dialog.showMessageBox(win, options);
|
|
}
|
|
else {
|
|
electron_1.dialog.showMessageBox(options);
|
|
}
|
|
}
|
|
isManualCheck = false;
|
|
});
|
|
electron_updater_1.autoUpdater.on('download-progress', () => {
|
|
broadcastState({ type: 'downloading' });
|
|
updateMenuState(MenuUpdateStep.DownloadingUpdate);
|
|
});
|
|
electron_updater_1.autoUpdater.on('update-downloaded', (info) => {
|
|
console.log(`[AutoUpdater] Update downloaded: ${info.version}`);
|
|
if (isHeadless) {
|
|
// Proceed to auto install in headless mode
|
|
if (electron_1.app.isPackaged) {
|
|
if (process.platform === 'linux') {
|
|
const downloadedFilePath = info.downloadedFile;
|
|
headlessQuitAndInstall(downloadedFilePath);
|
|
}
|
|
else {
|
|
electron_updater_1.autoUpdater.quitAndInstall();
|
|
}
|
|
}
|
|
else {
|
|
console.log('[AutoUpdater] Headless mode: Skipping quitAndInstall (not packaged).');
|
|
}
|
|
return;
|
|
}
|
|
broadcastState({
|
|
type: 'ready',
|
|
update: { version: info.version },
|
|
});
|
|
updateMenuState(MenuUpdateStep.RestartToUpdate);
|
|
});
|
|
electron_updater_1.autoUpdater.on('error', (err) => {
|
|
console.error('[AutoUpdater] Error:', err.message);
|
|
broadcastState({ type: 'idle' });
|
|
updateMenuState(MenuUpdateStep.CheckForUpdates);
|
|
isManualCheck = false;
|
|
});
|
|
// Schedule periodic checks
|
|
setTimeout(() => {
|
|
checkForUpdates();
|
|
setInterval(checkForUpdates, CHECK_INTERVAL_MS);
|
|
}, INITIAL_CHECK_DELAY_MS);
|
|
}
|
|
function checkForUpdates(isManual = false) {
|
|
isManualCheck = isManual;
|
|
electron_updater_1.autoUpdater.checkForUpdates().catch((err) => {
|
|
console.error('[AutoUpdater] Failed to check for updates:', err.message);
|
|
});
|
|
}
|
|
function quitAndInstall() {
|
|
electron_updater_1.autoUpdater.quitAndInstall();
|
|
}
|
|
/**
|
|
* Electron native quitAndInstall doesn't relaunch the app with command line arguments.
|
|
* This function waits for the app process to quit, manually replaces the executable with
|
|
* the downloaded update, and then relaunches it with the right headless flags.
|
|
*/
|
|
function headlessQuitAndInstall(downloadedFilePath) {
|
|
console.log('[AutoUpdater] Headless mode: Scheduling post-quit restart.');
|
|
try {
|
|
const currentPid = process.pid;
|
|
const appPath = process.env.APPIMAGE || process.execPath;
|
|
const args = [
|
|
'--ozone-platform=headless',
|
|
'--headless',
|
|
'--disable-gpu',
|
|
'--no-sandbox',
|
|
];
|
|
let script = '';
|
|
if (downloadedFilePath) {
|
|
console.log(`[AutoUpdater] Will manually replace ${appPath} with ${downloadedFilePath}`);
|
|
script = `
|
|
while kill -0 ${currentPid} 2>/dev/null; do sleep 0.5; done
|
|
cp -f "${downloadedFilePath}" "${appPath}"
|
|
chmod +x "${appPath}"
|
|
"${appPath}" ${args.join(' ')}
|
|
`;
|
|
}
|
|
else {
|
|
console.warn('[AutoUpdater] No downloaded file path found, relaunching without update.');
|
|
script = `
|
|
while kill -0 ${currentPid} 2>/dev/null; do sleep 0.5; done
|
|
sleep 3
|
|
"${appPath}" ${args.join(' ')}
|
|
`;
|
|
}
|
|
const child = (0, child_process_1.spawn)('sh', ['-c', script], {
|
|
detached: true,
|
|
stdio: 'ignore',
|
|
env: { ...process.env, ELECTRON_OZONE_PLATFORM_HINT: 'headless' },
|
|
});
|
|
child.unref();
|
|
}
|
|
catch (e) {
|
|
console.error('[AutoUpdater] Failed to schedule restart:', e);
|
|
}
|
|
electron_1.app.quit();
|
|
}
|