Skip to content

Commit f953b4f

Browse files
committed
fix(updates): use proper semver comparison and deduplicate notifications
1 parent 034f6c2 commit f953b4f

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

src/main/menu/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { repository } from '../../../package.json'
1212
import i18n from '../i18n'
1313
import { send } from '../ipc'
14-
import { fethUpdates } from '../updates'
14+
import { fetchUpdates } from '../updates'
1515
import { createMenu, createPlatformMenuItems } from './utils'
1616

1717
const year = new Date().getFullYear()
@@ -47,7 +47,7 @@ const appMenuItems: MenuConfig[] = [
4747
id: 'update',
4848
label: i18n.t('menu:app.update'),
4949
click: async () => {
50-
const latestVersion = await fethUpdates()
50+
const latestVersion = await fetchUpdates()
5151

5252
if (latestVersion) {
5353
const buttonId = dialog.showMessageBoxSync(

src/main/store/module/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ export default new Store<AppStore>({
1616
state: {},
1717
isAutoMigratedFromJson: false,
1818
lastSeenReleaseNoticeVersion: '',
19+
lastNotifiedUpdateVersion: '',
1920
},
2021
})

src/main/store/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface AppStore {
1818
isAutoMigratedFromJson: boolean
1919
nextDonateNotification?: number
2020
lastSeenReleaseNoticeVersion?: string
21+
lastNotifiedUpdateVersion?: string
2122
}
2223

2324
export interface EditorSettings {

src/main/updates/index.ts

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,77 @@
11
/* eslint-disable node/prefer-global/process */
22
import { repository, version } from '../../../package.json'
33
import { send } from '../ipc'
4+
import { store } from '../store'
45

56
interface GitHubRelease {
67
tag_name: string
78
}
89

910
const INTERVAL = 1000 * 60 * 60 * 3 // 3 часа
1011
const isDev = process.env.NODE_ENV === 'development'
12+
const currentVersionParts = parseVersion(version)!
13+
const currentMajorVersion = currentVersionParts[0]
1114

12-
export async function fethUpdates() {
15+
function parseVersion(rawVersion: string): [number, number, number] | null {
16+
const normalizedVersion = rawVersion.trim().replace(/^v/, '')
17+
const match = normalizedVersion.match(/^(\d+)\.(\d+)\.(\d+)$/)
18+
19+
if (!match) {
20+
return null
21+
}
22+
23+
return [
24+
Number.parseInt(match[1], 10),
25+
Number.parseInt(match[2], 10),
26+
Number.parseInt(match[3], 10),
27+
]
28+
}
29+
30+
function compareVersions(
31+
left: [number, number, number],
32+
right: [number, number, number],
33+
): 1 | -1 | 0 {
34+
for (let i = 0; i < 3; i += 1) {
35+
if (left[i] === right[i]) {
36+
continue
37+
}
38+
39+
return left[i] > right[i] ? 1 : -1
40+
}
41+
42+
return 0
43+
}
44+
45+
function getLatestReleaseVersion(releases: GitHubRelease[]) {
46+
let latestParsedVersion: [number, number, number] | null = null
47+
48+
for (const release of releases) {
49+
const parsedVersion = parseVersion(release.tag_name)
50+
if (!parsedVersion || parsedVersion[0] !== currentMajorVersion) {
51+
continue
52+
}
53+
54+
if (
55+
!latestParsedVersion
56+
|| compareVersions(parsedVersion, latestParsedVersion) > 0
57+
) {
58+
latestParsedVersion = parsedVersion
59+
}
60+
}
61+
62+
return latestParsedVersion?.join('.')
63+
}
64+
65+
function isNewerVersion(versionToCompare: string) {
66+
const parsedVersion = parseVersion(versionToCompare)
67+
if (!parsedVersion) {
68+
return false
69+
}
70+
71+
return compareVersions(parsedVersion, currentVersionParts) > 0
72+
}
73+
74+
export async function fetchUpdates() {
1375
if (isDev) {
1476
return
1577
}
@@ -18,36 +80,44 @@ export async function fethUpdates() {
1880
const url = `${repository.replace('github.com', 'api.github.com/repos')}/releases`
1981

2082
const response = await fetch(url)
21-
const data = (await response.json()) as GitHubRelease[]
22-
23-
if (!data) {
83+
if (!response.ok) {
2484
return
2585
}
2686

27-
const releases = data.filter((release) => {
28-
const tagName = release.tag_name.replace('v', '')
29-
return tagName.startsWith('4.')
30-
})
31-
32-
if (releases.length > 0) {
33-
const latestRelease = releases[0]
34-
const latestVersion = latestRelease.tag_name.replace('v', '')
87+
const data = (await response.json()) as GitHubRelease[]
88+
if (!Array.isArray(data) || data.length === 0) {
89+
return
90+
}
3591

36-
if (latestVersion !== version) {
37-
send('system:update-available')
38-
return latestVersion as string
39-
}
92+
const latestVersion = getLatestReleaseVersion(data)
93+
if (latestVersion && isNewerVersion(latestVersion)) {
94+
return latestVersion
4095
}
4196
}
4297
catch (err) {
4398
console.error('Error checking for updates:', err)
4499
}
45100
}
46101

102+
async function notifyAboutUpdate() {
103+
const latestVersion = await fetchUpdates()
104+
if (!latestVersion) {
105+
return
106+
}
107+
108+
const lastNotifiedVersion = store.app.get('lastNotifiedUpdateVersion')
109+
if (lastNotifiedVersion === latestVersion) {
110+
return
111+
}
112+
113+
send('system:update-available')
114+
store.app.set('lastNotifiedUpdateVersion', latestVersion)
115+
}
116+
47117
export function checkForUpdates() {
48-
fethUpdates()
118+
void notifyAboutUpdate()
49119

50120
setInterval(() => {
51-
fethUpdates()
121+
void notifyAboutUpdate()
52122
}, INTERVAL)
53123
}

0 commit comments

Comments
 (0)