mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-31 19:45:15 +02:00
feat(auto-updater, ui): implement update notification and installation prompt in desktop application
This commit is contained in:
parent
74fff64779
commit
c0fefa4db1
7 changed files with 155 additions and 18 deletions
|
|
@ -2,6 +2,8 @@ export const IPC_CHANNELS = {
|
|||
OPEN_EXTERNAL: 'open-external',
|
||||
GET_APP_VERSION: 'get-app-version',
|
||||
DEEP_LINK: 'deep-link',
|
||||
UPDATE_DOWNLOADED: 'update:downloaded',
|
||||
UPDATE_INSTALL_NOW: 'update:install-now',
|
||||
QUICK_ASK_TEXT: 'quick-ask-text',
|
||||
SET_QUICK_ASK_MODE: 'set-quick-ask-mode',
|
||||
GET_QUICK_ASK_MODE: 'get-quick-ask-mode',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import {
|
|||
stopAgentFilesystemTreeWatch,
|
||||
type AgentFilesystemTreeWatchOptions,
|
||||
} from '../modules/agent-filesystem-tree-watcher';
|
||||
import { installDownloadedUpdate } from '../modules/auto-updater';
|
||||
|
||||
let authTokens: { bearer: string; refresh: string } | null = null;
|
||||
|
||||
|
|
@ -70,6 +71,10 @@ export function registerIpcHandlers(): void {
|
|||
return app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.UPDATE_INSTALL_NOW, () => {
|
||||
installDownloadedUpdate();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_PERMISSIONS_STATUS, () => {
|
||||
return getPermissionsStatus();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { app, dialog } from 'electron';
|
||||
import { app, BrowserWindow, dialog } from 'electron';
|
||||
import { IPC_CHANNELS } from '../ipc/channels';
|
||||
import { trackEvent } from './analytics';
|
||||
|
||||
const SEMVER_RE = /^\d+\.\d+\.\d+/;
|
||||
|
|
@ -17,6 +18,7 @@ type UpdateInfo = {
|
|||
};
|
||||
|
||||
let listenersRegistered = false;
|
||||
let manualUpdateCheckInProgress = false;
|
||||
|
||||
function getAutoUpdater(): AutoUpdater {
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
|
|
@ -45,20 +47,9 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void {
|
|||
current_version: version,
|
||||
new_version: info.version,
|
||||
});
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
buttons: ['Restart', 'Later'],
|
||||
defaultId: 0,
|
||||
title: 'Update Ready',
|
||||
message: `Version ${info.version} has been downloaded. Restart to apply the update.`,
|
||||
}).then(({ response }: { response: number }) => {
|
||||
if (response === 0) {
|
||||
trackEvent('desktop_update_install_accepted', { new_version: info.version });
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
trackEvent('desktop_update_install_deferred', { new_version: info.version });
|
||||
}
|
||||
});
|
||||
if (!manualUpdateCheckInProgress) {
|
||||
notifyRenderersUpdateDownloaded(info);
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (err: Error) => {
|
||||
|
|
@ -69,6 +60,39 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void {
|
|||
});
|
||||
}
|
||||
|
||||
function notifyRenderersUpdateDownloaded(info: UpdateInfo): void {
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
if (!win.isDestroyed()) {
|
||||
win.webContents.send(IPC_CHANNELS.UPDATE_DOWNLOADED, {
|
||||
version: info.version,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function showNativeInstallDialog(autoUpdater: AutoUpdater, info: UpdateInfo): Promise<void> {
|
||||
const { response } = await dialog.showMessageBox({
|
||||
type: 'info',
|
||||
buttons: ['Restart', 'Later'],
|
||||
defaultId: 0,
|
||||
title: 'Update Ready',
|
||||
message: `Version ${info.version} has been downloaded. Restart to apply the update.`,
|
||||
});
|
||||
|
||||
if (response === 0) {
|
||||
trackEvent('desktop_update_install_accepted', { new_version: info.version });
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
trackEvent('desktop_update_install_deferred', { new_version: info.version });
|
||||
}
|
||||
}
|
||||
|
||||
export function installDownloadedUpdate(): void {
|
||||
const autoUpdater = getAutoUpdater();
|
||||
trackEvent('desktop_update_install_accepted', { source: 'renderer_prompt' });
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
export function setupAutoUpdater(): void {
|
||||
if (!app.isPackaged) return;
|
||||
|
||||
|
|
@ -108,25 +132,31 @@ export async function checkForUpdatesManually(): Promise<void> {
|
|||
configureAutoUpdater(autoUpdater);
|
||||
|
||||
try {
|
||||
const result = await new Promise<'available' | 'not-available'>((resolve, reject) => {
|
||||
manualUpdateCheckInProgress = true;
|
||||
const result = await new Promise<'not-available' | 'downloaded'>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
manualUpdateCheckInProgress = false;
|
||||
autoUpdater.removeListener('update-available', onAvailable);
|
||||
autoUpdater.removeListener('update-not-available', onNotAvailable);
|
||||
autoUpdater.removeListener('update-downloaded', onDownloaded);
|
||||
autoUpdater.removeListener('error', onError);
|
||||
};
|
||||
const onAvailable = (info: UpdateInfo) => {
|
||||
cleanup();
|
||||
void dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Update Available',
|
||||
message: `Version ${info.version} is available and will download in the background.`,
|
||||
});
|
||||
resolve('available');
|
||||
};
|
||||
const onNotAvailable = () => {
|
||||
cleanup();
|
||||
resolve('not-available');
|
||||
};
|
||||
const onDownloaded = (info: UpdateInfo) => {
|
||||
cleanup();
|
||||
void showNativeInstallDialog(autoUpdater, info);
|
||||
resolve('downloaded');
|
||||
};
|
||||
const onError = (err: Error) => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
|
|
@ -134,6 +164,7 @@ export async function checkForUpdatesManually(): Promise<void> {
|
|||
|
||||
autoUpdater.once('update-available', onAvailable);
|
||||
autoUpdater.once('update-not-available', onNotAvailable);
|
||||
autoUpdater.once('update-downloaded', onDownloaded);
|
||||
autoUpdater.once('error', onError);
|
||||
autoUpdater.checkForUpdates().catch((err: Error) => {
|
||||
cleanup();
|
||||
|
|
@ -149,6 +180,7 @@ export async function checkForUpdatesManually(): Promise<void> {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
manualUpdateCheckInProgress = false;
|
||||
await dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: 'Update Check Failed',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
},
|
||||
openExternal: (url: string) => ipcRenderer.send(IPC_CHANNELS.OPEN_EXTERNAL, url),
|
||||
getAppVersion: () => ipcRenderer.invoke(IPC_CHANNELS.GET_APP_VERSION),
|
||||
onUpdateDownloaded: (callback: (data: { version: string }) => void) => {
|
||||
const listener = (_event: unknown, data: { version: string }) => callback(data);
|
||||
ipcRenderer.on(IPC_CHANNELS.UPDATE_DOWNLOADED, listener);
|
||||
return () => {
|
||||
ipcRenderer.removeListener(IPC_CHANNELS.UPDATE_DOWNLOADED, listener);
|
||||
};
|
||||
},
|
||||
installUpdateNow: () => ipcRenderer.invoke(IPC_CHANNELS.UPDATE_INSTALL_NOW),
|
||||
onDeepLink: (callback: (url: string) => void) => {
|
||||
const listener = (_event: unknown, url: string) => callback(url);
|
||||
ipcRenderer.on(IPC_CHANNELS.DEEP_LINK, listener);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue