From 8d51cd5029707894d36dac46e3461c76fa0d9874 Mon Sep 17 00:00:00 2001 From: V Date: Mon, 10 Apr 2023 22:53:44 +0200 Subject: [PATCH] Add basic update notifications (#9) --- scripts/build/build.mts | 5 ++ src/main/index.ts | 2 + src/main/ipc.ts | 8 +- src/main/splash.ts | 11 +-- src/main/utils/vencordLoader.ts | 20 +++-- src/preload/VencordDesktopNative.ts | 10 +-- src/preload/typedIpcs.ts | 16 ++++ src/shared/IpcEvents.ts | 8 +- src/shared/browserWinProperties.ts | 18 +++++ src/shared/settings.d.ts | 1 + src/updater/main.ts | 98 ++++++++++++++++++++++++ src/updater/preload.ts | 21 +++++ static/updater.html | 115 ++++++++++++++++++++++++++++ 13 files changed, 305 insertions(+), 28 deletions(-) create mode 100644 src/preload/typedIpcs.ts create mode 100644 src/shared/browserWinProperties.ts create mode 100644 src/updater/main.ts create mode 100644 src/updater/preload.ts create mode 100644 static/updater.html diff --git a/scripts/build/build.mts b/scripts/build/build.mts index 54f80cb..11c4d5f 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -42,6 +42,11 @@ await Promise.all([ entryPoints: ["src/preload/index.ts"], outfile: "dist/js/preload.js" }), + createContext({ + ...NodeCommonOpts, + entryPoints: ["src/updater/preload.ts"], + outfile: "dist/js/updaterPreload.js" + }), createContext({ ...CommonOpts, globalName: "VencordDesktop", diff --git a/src/main/index.ts b/src/main/index.ts index eccfb75..c10ccb4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,6 +8,7 @@ import "./ipc"; import { app, BrowserWindow } from "electron"; import { join } from "path"; +import { checkUpdates } from "updater/main"; import { ICON_PATH } from "../shared/paths"; import { once } from "../shared/utils/once"; @@ -40,6 +41,7 @@ if (!app.requestSingleInstanceLock()) { }); app.whenReady().then(async () => { + checkUpdates(); if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop"); else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 81db39c..83c4d58 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -52,8 +52,12 @@ ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => { shell.showItemInFolder(path); }); -ipcMain.handle(IpcEvents.FOCUS, () => { - mainWin?.focus(); +ipcMain.handle(IpcEvents.FOCUS, e => { + e.sender.focus(); +}); + +ipcMain.handle(IpcEvents.CLOSE, e => { + e.sender.close(); }); ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => { diff --git a/src/main/splash.ts b/src/main/splash.ts index 6c21c6c..b158a58 100644 --- a/src/main/splash.ts +++ b/src/main/splash.ts @@ -6,18 +6,11 @@ import { BrowserWindow } from "electron"; import { join } from "path"; +import { SplashProps } from "shared/browserWinProperties"; import { STATIC_DIR } from "shared/paths"; export function createSplashWindow() { - const splash = new BrowserWindow({ - transparent: true, - frame: false, - height: 350, - width: 300, - center: true, - resizable: false, - maximizable: false - }); + const splash = new BrowserWindow(SplashProps); splash.loadFile(join(STATIC_DIR, "splash.html")); diff --git a/src/main/utils/vencordLoader.ts b/src/main/utils/vencordLoader.ts index 8799280..0d88841 100644 --- a/src/main/utils/vencordLoader.ts +++ b/src/main/utils/vencordLoader.ts @@ -11,10 +11,20 @@ import { join } from "path"; import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; import { downloadFile, simpleGet } from "./http"; -const API_BASE = "https://api.github.com/repos/Vendicated/Vencord"; +const API_BASE = "https://api.github.com"; const FILES_TO_DOWNLOAD = ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"]; +export interface ReleaseData { + name: string; + tag_name: string; + html_url: string; + assets: Array<{ + name: string; + browser_download_url: string; + }>; +} + export async function githubGet(endpoint: string) { const opts: RequestOptions = { headers: { @@ -29,13 +39,9 @@ export async function githubGet(endpoint: string) { } export async function downloadVencordFiles() { - const release = await githubGet("/releases/latest"); + const release = await githubGet("/repos/Vendicated/Vencord/releases/latest"); - const data = JSON.parse(release.toString("utf-8")); - const assets = data.assets as Array<{ - name: string; - browser_download_url: string; - }>; + const { assets } = JSON.parse(release.toString("utf-8")) as ReleaseData; await Promise.all( assets diff --git a/src/preload/VencordDesktopNative.ts b/src/preload/VencordDesktopNative.ts index 2daaa23..a4a23f4 100644 --- a/src/preload/VencordDesktopNative.ts +++ b/src/preload/VencordDesktopNative.ts @@ -4,19 +4,11 @@ * Copyright (c) 2023 Vendicated and Vencord contributors */ -import { ipcRenderer } from "electron"; import type { Settings } from "shared/settings"; import type { LiteralUnion } from "type-fest"; import { IpcEvents } from "../shared/IpcEvents"; - -function invoke(event: IpcEvents, ...args: any[]) { - return ipcRenderer.invoke(event, ...args) as Promise; -} - -function sendSync(event: IpcEvents, ...args: any[]) { - return ipcRenderer.sendSync(event, ...args) as T; -} +import { invoke, sendSync } from "./typedIpcs"; export const VencordDesktopNative = { app: { diff --git a/src/preload/typedIpcs.ts b/src/preload/typedIpcs.ts new file mode 100644 index 0000000..6522de6 --- /dev/null +++ b/src/preload/typedIpcs.ts @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { ipcRenderer } from "electron"; +import { IpcEvents } from "shared/IpcEvents"; + +export function invoke(event: IpcEvents, ...args: any[]) { + return ipcRenderer.invoke(event, ...args) as Promise; +} + +export function sendSync(event: IpcEvents, ...args: any[]) { + return ipcRenderer.sendSync(event, ...args) as T; +} diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index d324315..09ab674 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -19,5 +19,11 @@ export const enum IpcEvents { GET_SETTINGS = "VCD_GET_SETTINGS", SET_SETTINGS = "VCD_SET_SETTINGS", - SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR" + SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR", + + UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA", + UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD", + UPDATE_IGNORE = "VCD_UPDATE_IGNORE", + + CLOSE = "VCD_CLOSE" } diff --git a/src/shared/browserWinProperties.ts b/src/shared/browserWinProperties.ts new file mode 100644 index 0000000..a263db0 --- /dev/null +++ b/src/shared/browserWinProperties.ts @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import type { BrowserWindowConstructorOptions } from "electron"; + +export const SplashProps: BrowserWindowConstructorOptions = { + transparent: true, + frame: false, + height: 350, + width: 300, + center: true, + resizable: false, + maximizable: false, + alwaysOnTop: true +}; diff --git a/src/shared/settings.d.ts b/src/shared/settings.d.ts index cae4826..2f310b1 100644 --- a/src/shared/settings.d.ts +++ b/src/shared/settings.d.ts @@ -16,4 +16,5 @@ export interface Settings { disableMinSize?: boolean; tray?: boolean; minimizeToTray?: boolean; + skippedUpdate?: string; } diff --git a/src/updater/main.ts b/src/updater/main.ts new file mode 100644 index 0000000..4c7d59b --- /dev/null +++ b/src/updater/main.ts @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { app, BrowserWindow, ipcMain, shell } from "electron"; +import { Settings } from "main/settings"; +import { githubGet, ReleaseData } from "main/utils/vencordLoader"; +import { join } from "path"; +import { SplashProps } from "shared/browserWinProperties"; +import { IpcEvents } from "shared/IpcEvents"; +import { STATIC_DIR } from "shared/paths"; + +export interface UpdateData { + currentVersion: string; + latestVersion: string; + release: ReleaseData; +} + +let updateData: UpdateData; + +ipcMain.handle(IpcEvents.UPDATER_GET_DATA, () => updateData); +ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => { + const { assets } = updateData.release; + const url = (() => { + switch (process.platform) { + case "win32": + return assets.find(a => a.name.endsWith(".exe"))!.browser_download_url; + case "darwin": + return assets.find(a => a.name.endsWith(".dmg"))!.browser_download_url; + case "linux": + return updateData.release.html_url; + default: + throw new Error(`Unsupported platform: ${process.platform}`); + } + })(); + + shell.openExternal(url); +}); + +ipcMain.handle(IpcEvents.UPDATE_IGNORE, () => { + Settings.store.skippedUpdate = updateData.latestVersion; +}); + +function isOutdated(oldVersion: string, newVersion: string) { + const oldParts = oldVersion.split("."); + const newParts = newVersion.split("."); + + if (oldParts.length !== newParts.length) + throw new Error(`Incompatible version strings (old: ${oldVersion}, new: ${newVersion})`); + + for (let i = 0; i < oldParts.length; i++) { + const oldPart = Number(oldParts[i]); + const newPart = Number(newParts[i]); + + if (isNaN(oldPart) || isNaN(newPart)) + throw new Error(`Invalid version string (old: ${oldVersion}, new: ${newVersion})`); + + if (oldPart < newPart) return true; + if (oldPart > newPart) return false; + } + + return false; +} + +export async function checkUpdates() { + // if (IS_DEV) return; + try { + const raw = await githubGet("/repos/Vencord/Desktop/releases/latest"); + const data = JSON.parse(raw.toString("utf-8")) as ReleaseData; + + const oldVersion = app.getVersion(); + const newVersion = data.tag_name.replace(/^v/, ""); + if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) { + updateData = { + currentVersion: oldVersion, + latestVersion: newVersion, + release: data + }; + + openNewUpdateWindow(); + } + } catch (e) { + console.error("AppUpdater: Failed to check for updates\n", e); + } +} + +function openNewUpdateWindow() { + const win = new BrowserWindow({ + ...SplashProps, + webPreferences: { + preload: join(__dirname, "updaterPreload.js") + } + }); + + win.loadFile(join(STATIC_DIR, "updater.html")); +} diff --git a/src/updater/preload.ts b/src/updater/preload.ts new file mode 100644 index 0000000..8b539b8 --- /dev/null +++ b/src/updater/preload.ts @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { contextBridge } from "electron"; +import { invoke } from "preload/typedIpcs"; +import { IpcEvents } from "shared/IpcEvents"; + +import type { UpdateData } from "./main"; + +contextBridge.exposeInMainWorld("Updater", { + getData: () => invoke(IpcEvents.UPDATER_GET_DATA), + download: () => { + invoke(IpcEvents.UPDATER_DOWNLOAD); + invoke(IpcEvents.CLOSE); + }, + ignore: () => invoke(IpcEvents.UPDATE_IGNORE), + close: () => invoke(IpcEvents.CLOSE) +}); diff --git a/static/updater.html b/static/updater.html new file mode 100644 index 0000000..89a8f15 --- /dev/null +++ b/static/updater.html @@ -0,0 +1,115 @@ + + + + + +
+
+

Update Available

+

There's a new update for Vencord Desktop! Update now to get new fixes and features!

+

+ Current: +
+ Latest: +

+
+ +
+ + +
+ + +
+
+
+ + +