Skip to content

Commit de48935

Browse files
committed
improve autoupdating logic and logging
1 parent e9668f0 commit de48935

4 files changed

Lines changed: 102 additions & 37 deletions

File tree

src/main/autoUpdater.ts

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { ipcMain, app } from 'electron'
1+
import { ipcMain, app, autoUpdater as nativeAutoUpdater } from 'electron'
22
import type { AppUpdater } from 'electron-updater'
33
import electronUpdater from 'electron-updater'
44
import log from 'electron-log'
55

6+
let updaterLifecycleLoggingRegistered = false
7+
68
export function getAutoUpdater(): AppUpdater {
79
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
810
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
@@ -13,37 +15,81 @@ export function getAutoUpdater(): AppUpdater {
1315
}
1416

1517
export function initializeAutoUpdater() {
16-
getAutoUpdater().autoDownload = true
17-
getAutoUpdater().autoInstallOnAppQuit = false
18-
getAutoUpdater().allowDowngrade = true
18+
registerUpdaterLifecycleLogging()
19+
20+
const updater = getAutoUpdater()
21+
updater.autoDownload = true
22+
updater.autoInstallOnAppQuit = false
23+
updater.allowDowngrade = true
24+
25+
log.info(
26+
`[updater] initialized (appVersion=${app.getVersion()}, isPackaged=${app.isPackaged}, platform=${process.platform})`
27+
)
1928
}
2029

2130
export function registerAutoUpdateListeners(mainWindow: Electron.BrowserWindow) {
31+
const updater = getAutoUpdater()
32+
2233
ipcMain.on('updateAutoUpdater', () => {
23-
// force dev update config
24-
getAutoUpdater().checkForUpdatesAndNotify()
34+
updater.checkForUpdatesAndNotify().catch((error) => {
35+
const message = error instanceof Error ? error.message : String(error)
36+
log.error(`[updater] checkForUpdatesAndNotify rejected: ${message}`)
37+
})
2538
})
2639

27-
getAutoUpdater().addListener('update-available', () => {
40+
updater.addListener('update-available', (info) => {
41+
log.info(`[updater] update-available (version=${info.version})`)
2842
mainWindow.webContents.send('updateAvailable')
2943
})
3044

31-
getAutoUpdater().addListener('update-not-available', () => {
45+
updater.addListener('update-not-available', () => {
3246
mainWindow.webContents.send('updateNotAvailable')
3347
})
3448

35-
getAutoUpdater().addListener('update-downloaded', () => {
49+
updater.addListener('update-downloaded', (info) => {
50+
log.info(`[updater] update-downloaded (version=${info.version})`)
3651
mainWindow.webContents.send('updateDownloaded')
3752
})
3853

39-
getAutoUpdater().addListener('error', (error) => {
54+
updater.addListener('error', (error) => {
55+
log.error(`[updater] error: ${error.message}`)
4056
mainWindow.webContents.send('updateError', error.message)
4157
})
4258

4359
ipcMain.on('installUpdate', () => {
44-
app.emit('before-quit')
45-
setTimeout(() => {
46-
getAutoUpdater().quitAndInstall()
47-
}, 500)
60+
log.info(`[updater] installUpdate IPC received, calling quitAndInstall()`)
61+
try {
62+
updater.quitAndInstall()
63+
} catch (error) {
64+
const message = error instanceof Error ? error.message : String(error)
65+
log.error(`[updater] quitAndInstall() threw: ${message}`)
66+
mainWindow.webContents.send('updateError', message)
67+
}
68+
})
69+
}
70+
71+
function registerUpdaterLifecycleLogging() {
72+
if (updaterLifecycleLoggingRegistered) {
73+
return
74+
}
75+
76+
updaterLifecycleLoggingRegistered = true
77+
78+
// These four events are the ones that tell us whether Squirrel.Mac is actually
79+
// being reached during an install. Keep them.
80+
nativeAutoUpdater.on('before-quit-for-update', () => {
81+
log.info(`[updater] native autoUpdater emitted before-quit-for-update`)
82+
})
83+
84+
app.on('before-quit', () => {
85+
log.info(`[updater] app emitted before-quit`)
86+
})
87+
88+
app.on('will-quit', () => {
89+
log.info(`[updater] app emitted will-quit`)
90+
})
91+
92+
app.on('quit', (_event, exitCode) => {
93+
log.info(`[updater] app emitted quit (exitCode=${exitCode})`)
4894
})
4995
}

src/main/index.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -202,37 +202,50 @@ app.on('window-all-closed', () => {
202202
}
203203
})
204204

205-
// Save active periods before the app quits
206-
app.on('before-quit', async (event) => {
207-
// Skip cleanup during E2E testing to avoid slow shutdown
205+
// Save active periods before quitting. Use app.quit() after the async save so
206+
// Squirrel.Mac can finish updater handoff; app.exit(0) bypasses it.
207+
let saveCompleted = false
208+
let saveInProgress = false
209+
210+
app.on('before-quit', (event) => {
208211
if (isE2ETesting()) {
209212
return
210213
}
211214

212-
event?.preventDefault()
215+
if (saveCompleted) {
216+
return
217+
}
213218

214-
try {
215-
console.log('App quitting - saving active periods...')
219+
event.preventDefault()
216220

217-
// Create a promise that rejects after 5 seconds
218-
const timeoutPromise = new Promise<never>((_, reject) => {
219-
setTimeout(() => reject(new Error('Save operation timeout after 5 seconds')), 5000)
221+
if (saveInProgress) {
222+
return
223+
}
224+
saveInProgress = true
225+
226+
saveActivePeriodsBeforeQuit()
227+
.catch((error) => {
228+
console.error('Error saving active periods on quit:', error)
229+
})
230+
.finally(() => {
231+
saveCompleted = true
232+
app.quit()
220233
})
234+
})
221235

222-
// Race the save operations against the timeout
223-
const savePromise = Promise.all([stopActivityTracking(), stopIdleMonitoring()])
236+
async function saveActivePeriodsBeforeQuit(): Promise<void> {
237+
console.log('App quitting - saving active periods...')
224238

225-
await Promise.race([savePromise, timeoutPromise])
239+
const timeoutPromise = new Promise<never>((_, reject) => {
240+
setTimeout(() => reject(new Error('Save operation timeout after 5 seconds')), 5000)
241+
})
226242

227-
console.log('Active periods saved successfully')
228-
} catch (error) {
229-
console.error('Error saving active periods on quit:', error)
230-
// Continue with quit even if save fails
231-
} finally {
232-
// Now allow the app to quit
233-
app.exit(0)
234-
}
235-
})
243+
const savePromise = Promise.all([stopActivityTracking(), stopIdleMonitoring()])
244+
245+
await Promise.race([savePromise, timeoutPromise])
246+
247+
console.log('Active periods saved successfully')
248+
}
236249

237250
// In this file you can include the rest of your app"s specific main process
238251
// code. You can also put them in separate files and require them here.

src/main/mainWindow.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { join } from 'path'
2-
import { app, BrowserWindow, ipcMain, shell } from 'electron'
2+
import { app, autoUpdater as nativeAutoUpdater, BrowserWindow, ipcMain, shell } from 'electron'
33
import { is } from '@electron-toolkit/utils'
44
import { isE2ETesting } from './env'
55

@@ -48,6 +48,9 @@ export function initializeMainWindow(icon: string) {
4848
app.on('before-quit', () => {
4949
forcequit = true
5050
})
51+
nativeAutoUpdater.on('before-quit-for-update', () => {
52+
forcequit = true
53+
})
5154

5255
mainWindow.on('ready-to-show', () => {
5356
if (!isE2ETesting()) {

src/main/miniWindow.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { join } from 'path'
2-
import { app, BrowserWindow, ipcMain } from 'electron'
2+
import { app, autoUpdater as nativeAutoUpdater, BrowserWindow, ipcMain } from 'electron'
33
import { isE2ETesting } from './env'
44

55
export function initializeMiniWindow(icon: string) {
@@ -53,4 +53,7 @@ export function registerMiniWindowListeners(miniWindow: BrowserWindow) {
5353
app.on('before-quit', () => {
5454
forcequit = true
5555
})
56+
nativeAutoUpdater.on('before-quit-for-update', () => {
57+
forcequit = true
58+
})
5659
}

0 commit comments

Comments
 (0)