11import { autoUpdater , UpdateInfo } from 'electron-updater' ;
22import { app , BrowserWindow } from 'electron' ;
3+ import { join } from 'path' ;
4+ import { existsSync , readFileSync , writeFileSync } from 'fs' ;
35
46/**
57 * UpdateManager — wraps electron-updater with safe defaults.
68 *
79 * - Only runs in packaged (production) builds; no-ops in dev mode.
810 * - Checks for updates immediately on app start and again every 4 hours.
9- * - Downloads updates silently; prompts the user before installing.
10- * - Forwards progress/status to the renderer via the focused window so the
11- * UI can surface a non-intrusive update badge.
11+ * - Downloads updates completely silently in the background.
12+ * - Only notifies renderer when download is complete (header button).
13+ * - Auto-installs pending updates on quit.
14+ * - Detects post-update launch and notifies renderer to show a popup.
1215 */
1316class UpdateManager {
1417 private checkInterval : NodeJS . Timeout | null = null ;
18+ private updateReady = false ;
1519 private readonly CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000 ; // 4 hours
1620
1721 initialize ( ) : void {
@@ -20,7 +24,6 @@ class UpdateManager {
2024 return ;
2125 }
2226
23- // Disable auto-downloading; we want to show a prompt first on macOS.
2427 autoUpdater . autoDownload = false ;
2528 autoUpdater . autoInstallOnAppQuit = true ;
2629
@@ -34,9 +37,66 @@ class UpdateManager {
3437 ) ;
3538 }
3639
40+ /**
41+ * Check if the app was just updated by comparing current version to
42+ * the last stored version. Sends 'update:just-updated' to the renderer
43+ * if a version change is detected.
44+ */
45+ notifyIfJustUpdated ( ) : void {
46+ const currentVersion = app . getVersion ( ) ;
47+ const lastVersionPath = join ( app . getPath ( 'userData' ) , '.last-version' ) ;
48+
49+ let justUpdated = false ;
50+ if ( existsSync ( lastVersionPath ) ) {
51+ try {
52+ const lastVersion = readFileSync ( lastVersionPath , 'utf-8' ) . trim ( ) ;
53+ if ( lastVersion && lastVersion !== currentVersion ) {
54+ justUpdated = true ;
55+ }
56+ } catch {
57+ // ignore read errors
58+ }
59+ }
60+
61+ writeFileSync ( lastVersionPath , currentVersion , 'utf-8' ) ;
62+
63+ if ( justUpdated ) {
64+ // Delay slightly so the renderer is ready to receive the event
65+ setTimeout ( ( ) => {
66+ this . send ( 'update:just-updated' , { version : currentVersion } ) ;
67+ } , 2000 ) ;
68+ }
69+ }
70+
71+ /** Returns true if an update has been downloaded and is ready to install. */
72+ isUpdateReady ( ) : boolean {
73+ return this . updateReady ;
74+ }
75+
76+ /** Installs the pending update and restarts the app. */
77+ installAndRestart ( ) : void {
78+ autoUpdater . quitAndInstall ( false , true ) ;
79+ }
80+
81+ /**
82+ * Called during graceful shutdown. If an update is downloaded,
83+ * install it so the next launch gets the new version.
84+ */
85+ installOnQuitIfReady ( ) : void {
86+ if ( this . updateReady ) {
87+ autoUpdater . quitAndInstall ( true , true ) ;
88+ }
89+ }
90+
91+ destroy ( ) : void {
92+ if ( this . checkInterval ) {
93+ clearInterval ( this . checkInterval ) ;
94+ this . checkInterval = null ;
95+ }
96+ }
97+
3798 private checkForUpdates ( ) : void {
3899 autoUpdater . checkForUpdates ( ) . catch ( ( err ) => {
39- // Update checks failing (e.g. no network) is non-fatal — log silently.
40100 console . error ( '[updater] Update check failed:' , err ?. message ?? err ) ;
41101 } ) ;
42102 }
@@ -52,10 +112,7 @@ class UpdateManager {
52112 private registerListeners ( ) : void {
53113 autoUpdater . on ( 'update-available' , ( info : UpdateInfo ) => {
54114 console . log ( `[updater] Update available: ${ info . version } ` ) ;
55- this . send ( 'update:available' , { version : info . version } ) ;
56-
57- // Ask the user before downloading via the renderer (or auto-download here).
58- // For now we trigger download immediately and let the renderer show progress.
115+ // Download silently — no UI shown to user
59116 autoUpdater . downloadUpdate ( ) . catch ( ( err ) => {
60117 console . error ( '[updater] Download failed:' , err ?. message ?? err ) ;
61118 } ) ;
@@ -66,37 +123,21 @@ class UpdateManager {
66123 } ) ;
67124
68125 autoUpdater . on ( 'download-progress' , ( progress ) => {
69- this . send ( 'update:download-progress' , {
70- percent : Math . round ( progress . percent ) ,
71- bytesPerSecond : progress . bytesPerSecond ,
72- transferred : progress . transferred ,
73- total : progress . total ,
74- } ) ;
126+ // Silent — no UI shown to user
127+ console . log ( `[updater] Download progress: ${ Math . round ( progress . percent ) } %` ) ;
75128 } ) ;
76129
77130 autoUpdater . on ( 'update-downloaded' , ( info : UpdateInfo ) => {
78131 console . log ( `[updater] Update downloaded: ${ info . version } ` ) ;
79- // Notify the renderer so it can show an "Install & restart" prompt.
132+ this . updateReady = true ;
133+ // Notify the renderer to show the header restart button
80134 this . send ( 'update:downloaded' , { version : info . version } ) ;
81135 } ) ;
82136
83137 autoUpdater . on ( 'error' , ( err : Error ) => {
84138 console . error ( '[updater] Error:' , err ?. message ?? err ) ;
85- this . send ( 'update:error' , { message : err ?. message ?? String ( err ) } ) ;
86139 } ) ;
87140 }
88-
89- /** Called by the renderer (via IPC) when the user accepts the update. */
90- installAndRestart ( ) : void {
91- autoUpdater . quitAndInstall ( false , true ) ;
92- }
93-
94- destroy ( ) : void {
95- if ( this . checkInterval ) {
96- clearInterval ( this . checkInterval ) ;
97- this . checkInterval = null ;
98- }
99- }
100141}
101142
102143export const updateManager = new UpdateManager ( ) ;
0 commit comments