Add basic update notifications (#9)
This commit is contained in:
parent
bfb9af05b0
commit
8d51cd5029
13 changed files with 305 additions and 28 deletions
|
@ -42,6 +42,11 @@ await Promise.all([
|
||||||
entryPoints: ["src/preload/index.ts"],
|
entryPoints: ["src/preload/index.ts"],
|
||||||
outfile: "dist/js/preload.js"
|
outfile: "dist/js/preload.js"
|
||||||
}),
|
}),
|
||||||
|
createContext({
|
||||||
|
...NodeCommonOpts,
|
||||||
|
entryPoints: ["src/updater/preload.ts"],
|
||||||
|
outfile: "dist/js/updaterPreload.js"
|
||||||
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...CommonOpts,
|
...CommonOpts,
|
||||||
globalName: "VencordDesktop",
|
globalName: "VencordDesktop",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import "./ipc";
|
||||||
|
|
||||||
import { app, BrowserWindow } from "electron";
|
import { app, BrowserWindow } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import { checkUpdates } from "updater/main";
|
||||||
|
|
||||||
import { ICON_PATH } from "../shared/paths";
|
import { ICON_PATH } from "../shared/paths";
|
||||||
import { once } from "../shared/utils/once";
|
import { once } from "../shared/utils/once";
|
||||||
|
@ -40,6 +41,7 @@ if (!app.requestSingleInstanceLock()) {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
|
checkUpdates();
|
||||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
||||||
else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH);
|
else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH);
|
||||||
|
|
||||||
|
|
|
@ -52,8 +52,12 @@ ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||||
shell.showItemInFolder(path);
|
shell.showItemInFolder(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.FOCUS, () => {
|
ipcMain.handle(IpcEvents.FOCUS, e => {
|
||||||
mainWin?.focus();
|
e.sender.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.CLOSE, e => {
|
||||||
|
e.sender.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||||
|
|
|
@ -6,18 +6,11 @@
|
||||||
|
|
||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import { SplashProps } from "shared/browserWinProperties";
|
||||||
import { STATIC_DIR } from "shared/paths";
|
import { STATIC_DIR } from "shared/paths";
|
||||||
|
|
||||||
export function createSplashWindow() {
|
export function createSplashWindow() {
|
||||||
const splash = new BrowserWindow({
|
const splash = new BrowserWindow(SplashProps);
|
||||||
transparent: true,
|
|
||||||
frame: false,
|
|
||||||
height: 350,
|
|
||||||
width: 300,
|
|
||||||
center: true,
|
|
||||||
resizable: false,
|
|
||||||
maximizable: false
|
|
||||||
});
|
|
||||||
|
|
||||||
splash.loadFile(join(STATIC_DIR, "splash.html"));
|
splash.loadFile(join(STATIC_DIR, "splash.html"));
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,20 @@ import { join } from "path";
|
||||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||||
import { downloadFile, simpleGet } from "./http";
|
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"];
|
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) {
|
export async function githubGet(endpoint: string) {
|
||||||
const opts: RequestOptions = {
|
const opts: RequestOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -29,13 +39,9 @@ export async function githubGet(endpoint: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadVencordFiles() {
|
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 } = JSON.parse(release.toString("utf-8")) as ReleaseData;
|
||||||
const assets = data.assets as Array<{
|
|
||||||
name: string;
|
|
||||||
browser_download_url: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
assets
|
assets
|
||||||
|
|
|
@ -4,19 +4,11 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
import type { Settings } from "shared/settings";
|
import type { Settings } from "shared/settings";
|
||||||
import type { LiteralUnion } from "type-fest";
|
import type { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
|
import { invoke, sendSync } from "./typedIpcs";
|
||||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
|
||||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
|
||||||
return ipcRenderer.sendSync(event, ...args) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VencordDesktopNative = {
|
export const VencordDesktopNative = {
|
||||||
app: {
|
app: {
|
||||||
|
|
16
src/preload/typedIpcs.ts
Normal file
16
src/preload/typedIpcs.ts
Normal file
|
@ -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<T = any>(event: IpcEvents, ...args: any[]) {
|
||||||
|
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
||||||
|
return ipcRenderer.sendSync(event, ...args) as T;
|
||||||
|
}
|
|
@ -19,5 +19,11 @@ export const enum IpcEvents {
|
||||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||||
SET_SETTINGS = "VCD_SET_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"
|
||||||
}
|
}
|
||||||
|
|
18
src/shared/browserWinProperties.ts
Normal file
18
src/shared/browserWinProperties.ts
Normal file
|
@ -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
|
||||||
|
};
|
1
src/shared/settings.d.ts
vendored
1
src/shared/settings.d.ts
vendored
|
@ -16,4 +16,5 @@ export interface Settings {
|
||||||
disableMinSize?: boolean;
|
disableMinSize?: boolean;
|
||||||
tray?: boolean;
|
tray?: boolean;
|
||||||
minimizeToTray?: boolean;
|
minimizeToTray?: boolean;
|
||||||
|
skippedUpdate?: string;
|
||||||
}
|
}
|
||||||
|
|
98
src/updater/main.ts
Normal file
98
src/updater/main.ts
Normal file
|
@ -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"));
|
||||||
|
}
|
21
src/updater/preload.ts
Normal file
21
src/updater/preload.ts
Normal file
|
@ -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<UpdateData>(IpcEvents.UPDATER_GET_DATA),
|
||||||
|
download: () => {
|
||||||
|
invoke<void>(IpcEvents.UPDATER_DOWNLOAD);
|
||||||
|
invoke<void>(IpcEvents.CLOSE);
|
||||||
|
},
|
||||||
|
ignore: () => invoke<void>(IpcEvents.UPDATE_IGNORE),
|
||||||
|
close: () => invoke<void>(IpcEvents.CLOSE)
|
||||||
|
});
|
115
static/updater.html
Normal file
115
static/updater.html
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||||
|
"Open Sans", "Helvetica Neue", sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgb(219, 222, 225);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #313338;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #248046;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5em;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: filter 0.2 ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:active {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: #248046;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
background-color: #ed4245;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<section>
|
||||||
|
<h1>Update Available</h1>
|
||||||
|
<p>There's a new update for Vencord Desktop! Update now to get new fixes and features!</p>
|
||||||
|
<p>
|
||||||
|
Current: <span id="current"></span>
|
||||||
|
<br />
|
||||||
|
Latest: <span id="latest"></span>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<label id="disable-remind">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<span>Do not remind again for </span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button name="download" class="green">Download Update</button>
|
||||||
|
<button name="close" class="red">Close</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const data = await Updater.getData();
|
||||||
|
document.getElementById("current").textContent = data.currentVersion;
|
||||||
|
document.getElementById("latest").textContent = data.latestVersion;
|
||||||
|
|
||||||
|
document.querySelector("#disable-remind > span").textContent += data.latestVersion;
|
||||||
|
|
||||||
|
function checkDisableRemind() {
|
||||||
|
const checkbox = document.querySelector("#disable-remind > input");
|
||||||
|
if (checkbox.checked) {
|
||||||
|
Updater.ignore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClicks = {
|
||||||
|
download() {
|
||||||
|
checkDisableRemind();
|
||||||
|
Updater.download();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
checkDisableRemind();
|
||||||
|
Updater.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const name in onClicks) {
|
||||||
|
document.querySelectorAll(`button[name="${name}"]`).forEach(button => {
|
||||||
|
button.addEventListener("click", onClicks[name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in a new issue