vee's tray updates

This commit is contained in:
thororen 2026-10-03 16:35:13 -04:00
parent ebf7242051
commit e8a11aa0a2
No known key found for this signature in database
10 changed files with 115 additions and 148 deletions

View file

@ -73,8 +73,8 @@ window.VencordNative = {
updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Equicord/Equicord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
fetchUpdate: async () => ({ ok: true, value: false }),
applyUpdate: async () => ({ ok: true, value: true }),
},
quickCss: {
@ -136,9 +136,4 @@ window.VencordNative = {
pluginHelpers: {} as any,
csp: {} as any,
tray: {
setUpdateState: NOOP,
onCheckUpdates: NOOP,
onRepair: NOOP,
},
};

View file

@ -38,13 +38,13 @@ import { StartAt } from "@utils/types";
import { SettingsRouter } from "@webpack/common";
import { get as dsGet } from "./api/DataStore";
import { popNotice, showNotice } from "./api/Notices";
import { showNotice } from "./api/Notices";
import { NotificationData, showNotification } from "./api/Notifications";
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
import { areLocalSettingsDirty, getCloudSettings, getCloudSyncDirection, markLocalSettingsDirty, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
import { relaunch } from "./utils/native";
import { checkForUpdates, isOutdated as getIsOutdated, update, UpdateLogger } from "./utils/updater";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { patches } from "./webpack/patchWebpack";
@ -137,7 +137,6 @@ async function runUpdateCheck() {
try {
const isOutdated = await checkForUpdates();
if (IS_DISCORD_DESKTOP) VencordNative.tray.setUpdateState(isOutdated);
if (!isOutdated) return;
if (Settings.autoUpdate) {
@ -168,43 +167,11 @@ async function runUpdateCheck() {
}
}
function initTrayIpc() {
if (IS_WEB || IS_UPDATER_DISABLED) return;
VencordNative.tray.onCheckUpdates(async () => {
try {
const isOutdated = await checkForUpdates();
VencordNative.tray.setUpdateState(isOutdated);
if (isOutdated) {
showNotice("An Equicord update is available!", "View Update", () => openSettingsTabModal(UpdaterTab!));
} else {
showNotice("No updates available, you're on the latest version!", "OK", popNotice);
}
} catch (err) {
UpdateLogger.error("Failed to check for updates from tray", err);
showNotice("Failed to check for updates, check the console for more info", "OK", popNotice);
}
});
VencordNative.tray.onRepair(async () => {
try {
await update();
relaunch();
} catch (err) {
UpdateLogger.error("Failed to repair Equicord", err);
}
});
VencordNative.tray.setUpdateState(getIsOutdated);
}
async function init() {
await onceReady;
startAllPlugins(StartAt.WebpackReady);
syncSettings();
initTrayIpc();
if (!IS_DEV && !IS_WEB && !IS_UPDATER_DISABLED) {
runUpdateCheck();

View file

@ -8,6 +8,7 @@ import type { Settings } from "@api/Settings";
import type { CspRequestResult } from "@main/csp/manager";
import type { PluginIpcMappings } from "@main/ipcPlugins";
import { UserThemeHeader } from "@main/themes";
import { UpdateData } from "@main/updater";
import { IpcEvents } from "@shared/IpcEvents";
import type { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron/renderer";
@ -46,10 +47,10 @@ export default {
},
updater: {
getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES),
update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE),
rebuild: () => invoke<IpcRes<boolean>>(IpcEvents.BUILD),
getRepo: () => invoke<IpcRes<string>>(IpcEvents.GET_REPO),
getRepo: () => invoke<IpcRes<string>>(IpcEvents.UPDATER_GET_REPO),
getUpdates: () => invoke<IpcRes<UpdateData[]>>(IpcEvents.UPDATER_LIST_UPDATES),
fetchUpdate: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATER_FETCH_UPDATE),
applyUpdate: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATER_APPLY_UPDATE),
},
settings: {
@ -101,11 +102,5 @@ export default {
invoke<CspRequestResult>(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName),
},
tray: {
setUpdateState: (available: boolean) => ipcRenderer.send(IpcEvents.SET_TRAY_UPDATE_STATE, available),
onCheckUpdates: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_CHECK_UPDATES, cb); },
onRepair: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_REPAIR, cb); },
},
pluginHelpers: PluginHelpers
};

View file

@ -354,8 +354,8 @@ function ChangelogContent() {
if (!logged) {
setChangelog([]);
showToast("Already up to date with repository", "message", {
position: ToastPosition.BOTTOM,
});
position: ToastPosition.BOTTOM,
});
}
return;
}
@ -381,16 +381,16 @@ function ChangelogContent() {
setRecentlyChecked(true);
showToast(`Found ${updates.value.length} commit${updates.value.length === 1 ? "" : "s"} from repository`, "success", {
position: ToastPosition.BOTTOM,
});
position: ToastPosition.BOTTOM,
});
} else {
const logged = await ensureLocalUpdateLogged();
setRecentlyChecked(true);
showToast(logged
? "Logged commits from your latest update"
: "Repository is up to date with your local copy", logged ? "success" : "message", {
position: ToastPosition.BOTTOM,
});
? "Logged commits from your latest update"
: "Repository is up to date with your local copy", logged ? "success" : "message", {
position: ToastPosition.BOTTOM,
});
if (!logged) {
setChangelog([]);
}
@ -409,8 +409,8 @@ function ChangelogContent() {
// funny little error toast hopefully doesn't happen!
showToast("Could not fetch commits from repository", "failure", {
position: ToastPosition.BOTTOM,
});
position: ToastPosition.BOTTOM,
});
} finally {
setIsLoading(false);
}
@ -502,8 +502,8 @@ function ChangelogContent() {
await loadChangelogHistory();
setShowHistory(false);
showToast("All logs have been cleared", "success", {
position: ToastPosition.BOTTOM,
});
position: ToastPosition.BOTTOM,
});
},
});
}}
@ -636,8 +636,8 @@ function ChangelogContent() {
),
);
showToast("Log has been cleared", "success", {
position: ToastPosition.BOTTOM,
});
position: ToastPosition.BOTTOM,
});
},
});
}}

View file

@ -4,27 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcEvents } from "@shared/IpcEvents";
import { gitHashShort } from "@shared/vencordUserAgent";
import { BrowserWindow, ipcMain, Menu, MenuItemConstructorOptions, shell } from "electron";
import { gitHash } from "@shared/vencordUserAgent";
import { app, BaseWindow, BrowserWindow, dialog, Menu, MenuItemConstructorOptions, shell } from "electron";
import aboutHtml from "file://about.html?minify";
import updater from "./updater";
import { SETTINGS_DIR, THEMES_DIR } from "./utils/constants";
let cachedUpdateAvailable = false;
ipcMain.on(IpcEvents.SET_TRAY_UPDATE_STATE, (_, available: boolean) => {
cachedUpdateAvailable = available;
});
function getMainWindow(): BrowserWindow | undefined {
return BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0];
}
function sendToRenderer(event: IpcEvents): void {
getMainWindow()?.webContents.send(event);
}
function findInsertIndex(template: MenuItemConstructorOptions[]): number {
const openIndex = template.findIndex(item => {
const label = item.label?.toLowerCase() ?? "";
@ -61,14 +47,11 @@ function openAboutWindow() {
return;
}
const height = 750;
const width = height * (4 / 3);
aboutWindow = new BrowserWindow({
center: true,
autoHideMenuBar: true,
height,
width
height: 525,
width: 900
});
aboutWindow.webContents.setWindowOpenHandler(({ url }) => {
@ -83,7 +66,7 @@ function openAboutWindow() {
const aboutParams = aboutHtml
.replaceAll("{{VERSION}}", VERSION)
.replaceAll("{{GIT_HASH}}", gitHashShort);
.replaceAll("{{GIT_HASH}}", gitHash); // change to gitHashShort if/when its added
const base64Html = Buffer.from(aboutParams).toString("base64");
aboutWindow.loadURL(`data:text/html;base64,${base64Html}`);
aboutWindow.on("closed", () => {
@ -91,7 +74,9 @@ function openAboutWindow() {
});
}
function createEquicordMenuItems(): MenuItemConstructorOptions[] {
const notify = (window: BaseWindow, title: string, message: string) => dialog.showMessageBox(window, { title, message });
function createVencordMenuItems(): MenuItemConstructorOptions[] {
return [
{
label: "Equicord",
@ -101,12 +86,28 @@ function createEquicordMenuItems(): MenuItemConstructorOptions[] {
click: () => openAboutWindow()
},
{
label: cachedUpdateAvailable ? "Update Equicord" : "Check for Updates",
click: () => sendToRenderer(IpcEvents.TRAY_CHECK_UPDATES)
},
{
label: "Repair Equicord",
click: () => sendToRenderer(IpcEvents.TRAY_REPAIR)
label: "Update Equicord",
async click(_item, window) {
if (!window) return;
try {
const updateAvailable = await updater.fetchUpdate();
if (!updateAvailable) {
return notify(window, "No Update Available", "You are already using the latest version of Equicord.");
}
const result = await updater.applyUpdate();
if (!result) {
return notify(window, "Update Failed", "Failed to apply the update for Equicord.");
}
await notify(window, "Update Successful", "Now relaunching Equicord to apply the update.");
app.relaunch();
app.exit();
} catch (e) {
notify(window, "Update Error", `An error occurred while updating Equicord.\n\n${String(e)}`);
}
}
},
{ type: "separator" },
{
@ -130,8 +131,8 @@ export function patchTrayMenu(): void {
const alreadyPatched = template.some(item => item.label === "Equicord");
if (isTrayMenu(template) && !alreadyPatched) {
const insertIndex = findInsertIndex(template);
const equicordItems = createEquicordMenuItems();
template.splice(insertIndex, 0, ...equicordItems);
const vencordItems = createVencordMenuItems();
template.splice(insertIndex, 0, ...vencordItems);
}
return originalBuildFromTemplate.call(this, template);

View file

@ -16,13 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@shared/IpcEvents";
import { execFile as cpExecFile } from "child_process";
import { ipcMain } from "electron";
import { join } from "path";
import { promisify } from "util";
import { serializeErrors } from "./common";
import { Updater } from ".";
const VENCORD_SRC_DIR = join(__dirname, "..");
const EQUICORD_DIR = join(__dirname, "../../");
@ -85,7 +83,11 @@ async function build() {
return !res.stderr.includes("Build failed");
}
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(getRepo));
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(pull));
ipcMain.handle(IpcEvents.BUILD, serializeErrors(build));
const GitUpdater: Updater = {
getRepo,
listUpdates: calculateGitChanges,
fetchUpdate: pull,
applyUpdate: build
};
export default GitUpdater;

View file

@ -17,15 +17,14 @@
*/
import { fetchBuffer, fetchJson } from "@main/utils/http";
import { IpcEvents } from "@shared/IpcEvents";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { ipcMain } from "electron";
import { writeFileSync } from "original-fs";
import gitHash from "~git-hash";
import gitRemote from "~git-remote";
import { ASAR_FILE, serializeErrors } from "./common";
import { Updater } from ".";
import { ASAR_FILE } from "./common";
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
let PendingUpdate: string | null = null;
@ -41,8 +40,8 @@ async function githubGet<T = any>(endpoint: string) {
});
}
async function calculateGitChanges() {
const isOutdated = await fetchUpdates();
async function listUpdates() {
const isOutdated = await fetchUpdate();
if (!isOutdated) return [];
const data = await githubGet(`/compare/${gitHash}...HEAD`);
@ -54,7 +53,7 @@ async function calculateGitChanges() {
}));
}
async function fetchUpdates() {
async function fetchUpdate() {
const data = await githubGet("/releases/latest");
const hash = data.name.slice(data.name.lastIndexOf(" ") + 1);
@ -67,7 +66,7 @@ async function fetchUpdates() {
return true;
}
async function applyUpdates() {
async function applyUpdate() {
if (!PendingUpdate) return true;
const data = await fetchBuffer(PendingUpdate);
@ -78,7 +77,11 @@ async function applyUpdates() {
return true;
}
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${gitRemote}`));
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(fetchUpdates));
ipcMain.handle(IpcEvents.BUILD, serializeErrors(applyUpdates));
const HttpUpdater: Updater = {
getRepo: async () => `https://github.com/${gitRemote}`,
listUpdates,
fetchUpdate,
applyUpdate
};
export default HttpUpdater;

View file

@ -1,20 +1,8 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcEvents } from "@shared/IpcEvents";
import { ipcMain } from "electron";
@ -23,9 +11,27 @@ import gitRemote from "~git-remote";
import { serializeErrors } from "./common";
if (!IS_UPDATER_DISABLED) {
require(IS_STANDALONE ? "./http" : "./git");
} else {
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${gitRemote}`));
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(() => []));
export type UpdateData = Record<"hash" | "message" | "author", string>;
export interface Updater {
getRepo(): Promise<string>;
listUpdates(): Promise<UpdateData[]>;
fetchUpdate(): Promise<boolean>;
applyUpdate(): Promise<boolean>;
}
const updater: Updater = IS_UPDATER_DISABLED
? {
getRepo: async () => `https://github.com/${gitRemote}`,
listUpdates: async () => [],
fetchUpdate: async () => false,
applyUpdate: async () => false,
}
: require(IS_STANDALONE ? "./http" : "./git").default;
ipcMain.handle(IpcEvents.UPDATER_GET_REPO, serializeErrors(updater.getRepo));
ipcMain.handle(IpcEvents.UPDATER_LIST_UPDATES, serializeErrors(updater.listUpdates));
ipcMain.handle(IpcEvents.UPDATER_FETCH_UPDATE, serializeErrors(updater.fetchUpdate));
ipcMain.handle(IpcEvents.UPDATER_APPLY_UPDATE, serializeErrors(updater.applyUpdate));
export default updater;

View file

@ -32,10 +32,12 @@ export const enum IpcEvents {
SET_SETTINGS = "VencordSetSettings",
THEME_UPDATE = "VencordThemeUpdate",
OPEN_EXTERNAL = "VencordOpenExternal",
GET_UPDATES = "VencordGetUpdates",
GET_REPO = "VencordGetRepo",
UPDATE = "VencordUpdate",
BUILD = "VencordBuild",
UPDATER_LIST_UPDATES = "VencordListUpdates",
UPDATER_GET_REPO = "VencordGetRepo",
UPDATER_FETCH_UPDATE = "VencordFetchUpdate",
UPDATER_APPLY_UPDATE = "VencordApplyUpdate",
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
GET_MONACO_THEME = "VencordGetMonacoTheme",
@ -51,9 +53,5 @@ export const enum IpcEvents {
RENDERER_CSS_UPDATE = "VencordRendererCssUpdate",
PRELOAD_GET_RENDERER_JS = "VencordPreloadGetRendererJs",
SET_TRAY_UPDATE_STATE = "VencordSetTrayUpdateState",
TRAY_REPAIR = "VencordTrayRepair",
TRAY_CHECK_UPDATES = "VencordTrayCheckUpdates",
TRAY_ABOUT = "VencordTrayAbout",
SUPPORTS_WINDOWS_MATERIAL = "VencordSupportsWindowsMaterial",
}

View file

@ -54,12 +54,12 @@ export async function checkForUpdates() {
export async function update() {
if (!isOutdated) return true;
const res = await Unwrap(VencordNative.updater.update());
const res = await Unwrap(VencordNative.updater.fetchUpdate());
if (res) {
isOutdated = false;
if (!await Unwrap(VencordNative.updater.rebuild()))
throw new Error("The Build failed. Please try manually building the new update");
if (!await Unwrap(VencordNative.updater.applyUpdate()))
throw new Error("Failed to apply the update.");
}
return res;