Skip to content

Commit 25613ec

Browse files
committed
feat: update indicator
1 parent a816ac8 commit 25613ec

11 files changed

Lines changed: 473 additions & 17 deletions

File tree

build/entitlements.mac.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
<true/>
99
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
1010
<true/>
11+
<key>com.apple.security.cs.disable-library-validation</key>
12+
<true/>
1113
</dict>
1214
</plist>

electron-builder.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ nsis:
1919
uninstallDisplayName: ${productName}ß
2020
createDesktopShortcut: always
2121
mac:
22+
artifactName: ${name}-${version}-${arch}-mac.${ext}
2223
entitlementsInherit: build/entitlements.mac.plist
2324
extendInfo:
2425
- NSCameraUsageDescription: Application requests access to the device's camera.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "op-notes",
3-
"version": "0.0.11",
3+
"version": "0.0.10",
44
"description": "Op Notes",
55
"main": "./out/main/index.js",
66
"author": "Kasun Vithanage",

src/main/index.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,10 @@ import icon from '../../resources/icon.png?asset'
55

66
import { db, migrateToLatest } from './db'
77
import { registerApi } from './api'
8+
import { setupAutoUpdater, registerUpdaterIpcHandlers } from './updater'
89

9-
import electronUpdater, { type AppUpdater } from 'electron-updater'
1010
import { PrintDialogArgs } from '../preload/interfaces'
1111

12-
export function getAutoUpdater(): AppUpdater {
13-
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
14-
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
15-
const { autoUpdater } = electronUpdater
16-
const log = require('electron-log')
17-
log.transports.file.level = 'debug'
18-
autoUpdater.logger = log
19-
return autoUpdater
20-
}
21-
2212
function createWindow() {
2313
// Create the browser window.
2414
const mainWindow = new BrowserWindow({
@@ -119,7 +109,16 @@ app.whenReady().then(() => {
119109
// Set app user model id for windows
120110
electronApp.setAppUserModelId('com.wavezync.opnotes')
121111

122-
getAutoUpdater().checkForUpdatesAndNotify()
112+
// Setup auto-updater with custom event handlers
113+
const autoUpdater = setupAutoUpdater()
114+
registerUpdaterIpcHandlers()
115+
116+
// Check for updates after a short delay to allow the window to initialize
117+
setTimeout(() => {
118+
autoUpdater.checkForUpdates().catch((err) => {
119+
console.error('Failed to check for updates:', err)
120+
})
121+
}, 3000)
123122

124123
// Default open or close DevTools by F12 in development
125124
// and ignore CommandOrControl + R in production.

src/main/updater.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { BrowserWindow, ipcMain } from 'electron'
2+
import electronUpdater, { type AppUpdater, type UpdateInfo } from 'electron-updater'
3+
4+
export type UpdateStatus =
5+
| 'idle'
6+
| 'checking'
7+
| 'available'
8+
| 'not-available'
9+
| 'downloading'
10+
| 'ready'
11+
| 'error'
12+
13+
export interface UpdateStatusPayload {
14+
status: UpdateStatus
15+
updateInfo?: UpdateInfo
16+
progress?: {
17+
percent: number
18+
bytesPerSecond: number
19+
transferred: number
20+
total: number
21+
}
22+
error?: string
23+
}
24+
25+
let autoUpdater: AppUpdater | null = null
26+
27+
function getMainWindow(): BrowserWindow | null {
28+
const windows = BrowserWindow.getAllWindows()
29+
return windows.length > 0 ? windows[0] : null
30+
}
31+
32+
function sendUpdateStatus(payload: UpdateStatusPayload): void {
33+
const mainWindow = getMainWindow()
34+
if (mainWindow && !mainWindow.isDestroyed()) {
35+
mainWindow.webContents.send('update-status', payload)
36+
}
37+
}
38+
39+
export function setupAutoUpdater(): AppUpdater {
40+
const { autoUpdater: updater } = electronUpdater
41+
const log = require('electron-log')
42+
log.transports.file.level = 'debug'
43+
updater.logger = log
44+
45+
updater.autoDownload = false
46+
updater.autoInstallOnAppQuit = true
47+
48+
autoUpdater = updater
49+
50+
updater.on('checking-for-update', () => {
51+
sendUpdateStatus({ status: 'checking' })
52+
})
53+
54+
updater.on('update-available', (info: UpdateInfo) => {
55+
sendUpdateStatus({ status: 'available', updateInfo: info })
56+
})
57+
58+
updater.on('update-not-available', (info: UpdateInfo) => {
59+
sendUpdateStatus({ status: 'not-available', updateInfo: info })
60+
})
61+
62+
updater.on('download-progress', (progress) => {
63+
sendUpdateStatus({
64+
status: 'downloading',
65+
progress: {
66+
percent: progress.percent,
67+
bytesPerSecond: progress.bytesPerSecond,
68+
transferred: progress.transferred,
69+
total: progress.total
70+
}
71+
})
72+
})
73+
74+
updater.on('update-downloaded', (info: UpdateInfo) => {
75+
sendUpdateStatus({ status: 'ready', updateInfo: info })
76+
})
77+
78+
updater.on('error', (error: Error) => {
79+
sendUpdateStatus({ status: 'error', error: error.message })
80+
})
81+
82+
return updater
83+
}
84+
85+
export function registerUpdaterIpcHandlers(): void {
86+
ipcMain.handle('checkForUpdates', async () => {
87+
if (!autoUpdater) {
88+
throw new Error('Auto updater not initialized')
89+
}
90+
return await autoUpdater.checkForUpdates()
91+
})
92+
93+
ipcMain.handle('downloadUpdate', async () => {
94+
if (!autoUpdater) {
95+
throw new Error('Auto updater not initialized')
96+
}
97+
await autoUpdater.downloadUpdate()
98+
})
99+
100+
ipcMain.handle('quitAndInstall', () => {
101+
if (!autoUpdater) {
102+
throw new Error('Auto updater not initialized')
103+
}
104+
autoUpdater.quitAndInstall()
105+
})
106+
}

src/preload/index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { ElectronAPI } from '@electron-toolkit/preload'
22
import type { ApiType } from '../main/api'
3-
import { PrintDialogArgs } from './interfaces'
3+
import { PrintDialogArgs, UpdateStatusPayload } from './interfaces'
44

55
type ElectronApi = ElectronAPI & {
66
getAppVersion: () => Promise<string>
77
boot: () => Promise<boolean>
88
openPrintDialog: (options: PrintDialogArgs) => Promise<void>
99
onPrintData: (callback: (options: PrintDialogArgs) => void) => void
10+
checkForUpdates: () => Promise<unknown>
11+
downloadUpdate: () => Promise<void>
12+
quitAndInstall: () => Promise<void>
13+
onUpdateStatus: (callback: (payload: UpdateStatusPayload) => void) => () => void
1014
}
1115

1216
declare global {

src/preload/index.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { contextBridge, ipcRenderer } from 'electron'
22
import { electronAPI } from '@electron-toolkit/preload'
3-
import { PrintDialogArgs } from './interfaces'
3+
import { PrintDialogArgs, UpdateStatusPayload } from './interfaces'
44

55
// Use `contextBridge` APIs to expose Electron APIs to
66
// renderer only if context isolation is enabled, otherwise
@@ -29,12 +29,36 @@ const openPrintDialog = async (options: PrintDialogArgs) => {
2929
const onPrintData = (callback: (options: PrintDialogArgs) => void) =>
3030
ipcRenderer.on('printData', (_, options) => callback(options))
3131

32+
const checkForUpdates = async () => {
33+
return await ipcRenderer.invoke('checkForUpdates')
34+
}
35+
36+
const downloadUpdate = async () => {
37+
return await ipcRenderer.invoke('downloadUpdate')
38+
}
39+
40+
const quitAndInstall = () => {
41+
return ipcRenderer.invoke('quitAndInstall')
42+
}
43+
44+
const onUpdateStatus = (callback: (payload: UpdateStatusPayload) => void) => {
45+
const handler = (_: Electron.IpcRendererEvent, payload: UpdateStatusPayload) => callback(payload)
46+
ipcRenderer.on('update-status', handler)
47+
return () => {
48+
ipcRenderer.removeListener('update-status', handler)
49+
}
50+
}
51+
3252
const electronApi = {
3353
...electronAPI,
3454
getAppVersion,
3555
boot,
3656
openPrintDialog,
37-
onPrintData
57+
onPrintData,
58+
checkForUpdates,
59+
downloadUpdate,
60+
quitAndInstall,
61+
onUpdateStatus
3862
}
3963

4064
if (process.contextIsolated) {

src/preload/interfaces.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,29 @@ export interface PrintDialogArgs {
22
title?: string
33
data?: object
44
}
5+
6+
export type UpdateStatus =
7+
| 'idle'
8+
| 'checking'
9+
| 'available'
10+
| 'not-available'
11+
| 'downloading'
12+
| 'ready'
13+
| 'error'
14+
15+
export interface UpdateStatusPayload {
16+
status: UpdateStatus
17+
updateInfo?: {
18+
version: string
19+
releaseNotes?: string | null
20+
releaseName?: string | null
21+
releaseDate?: string
22+
}
23+
progress?: {
24+
percent: number
25+
bytesPerSecond: number
26+
transferred: number
27+
total: number
28+
}
29+
error?: string
30+
}

0 commit comments

Comments
 (0)