feat: adding api call for decky plugin

This commit is contained in:
Chubby Granny Chaser
2025-10-12 18:51:47 +01:00
parent dcec33ada1
commit 34aea2b0c4
8 changed files with 199 additions and 71 deletions

View File

@@ -78,6 +78,7 @@
"edit_game_modal_drop_to_replace_logo": "Drop to replace logo",
"edit_game_modal_drop_to_replace_hero": "Drop to replace hero",
"install_decky_plugin": "Install Decky Plugin",
"update_decky_plugin": "Update Decky Plugin",
"decky_plugin_installed_version": "Decky Plugin (v{{version}})",
"install_decky_plugin_title": "Install Hydra Decky Plugin",
"install_decky_plugin_message": "This will download and install the Hydra plugin for Decky Loader. This may require elevated permissions. Continue?",

View File

@@ -78,6 +78,7 @@
"edit_game_modal_drop_to_replace_logo": "Solte para substituir o logo",
"edit_game_modal_drop_to_replace_hero": "Solte para substituir o hero",
"install_decky_plugin": "Instalar Plugin Decky",
"update_decky_plugin": "Atualizar Plugin Decky",
"decky_plugin_installed_version": "Plugin Decky (v{{version}})",
"install_decky_plugin_title": "Instalar Plugin Hydra Decky",
"install_decky_plugin_message": "Isso irá baixar e instalar o plugin Hydra para Decky Loader. Pode ser necessário permissões elevadas. Continuar?",

View File

@@ -1,17 +1,37 @@
import { registerEvent } from "../register-event";
import { logger } from "@main/services";
import { logger, HydraApi } from "@main/services";
import { HYDRA_DECKY_PLUGIN_LOCATION } from "@main/constants";
import fs from "node:fs";
import path from "node:path";
interface DeckyReleaseInfo {
version: string;
downloadUrl: string;
}
const getHydraDeckyPluginInfo = async (
_event: Electron.IpcMainInvokeEvent
): Promise<{
installed: boolean;
version: string | null;
path: string;
outdated: boolean;
expectedVersion: string | null;
}> => {
try {
// Fetch the expected version from API
let expectedVersion: string | null = null;
try {
const releaseInfo = await HydraApi.get<DeckyReleaseInfo>(
"/decky/release",
{},
{ needsAuth: false }
);
expectedVersion = releaseInfo.version;
} catch (error) {
logger.error("Failed to fetch Decky release info:", error);
}
// Check if plugin folder exists
if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
logger.log("Hydra Decky plugin not installed");
@@ -19,6 +39,8 @@ const getHydraDeckyPluginInfo = async (
installed: false,
version: null,
path: HYDRA_DECKY_PLUGIN_LOCATION,
outdated: true,
expectedVersion,
};
}
@@ -34,6 +56,8 @@ const getHydraDeckyPluginInfo = async (
installed: false,
version: null,
path: HYDRA_DECKY_PLUGIN_LOCATION,
outdated: true,
expectedVersion,
};
}
@@ -42,12 +66,18 @@ const getHydraDeckyPluginInfo = async (
const packageJson = JSON.parse(packageJsonContent);
const version = packageJson.version;
logger.log(`Hydra Decky plugin installed, version: ${version}`);
const outdated = expectedVersion ? version !== expectedVersion : false;
logger.log(
`Hydra Decky plugin installed, version: ${version}, expected: ${expectedVersion}, outdated: ${outdated}`
);
return {
installed: true,
version,
path: HYDRA_DECKY_PLUGIN_LOCATION,
outdated,
expectedVersion,
};
} catch (error) {
logger.error("Failed to get plugin info:", error);
@@ -55,6 +85,8 @@ const getHydraDeckyPluginInfo = async (
installed: false,
version: null,
path: HYDRA_DECKY_PLUGIN_LOCATION,
outdated: true,
expectedVersion: null,
};
}
};

View File

@@ -41,7 +41,7 @@ const installHydraDeckyPlugin = async (
success: false,
path: HYDRA_DECKY_PLUGIN_LOCATION,
currentVersion: null,
expectedVersion: "0.0.3",
expectedVersion: "unknown",
error: errorMessage,
};
}

View File

@@ -11,11 +11,35 @@ import {
import { logger } from "./logger";
import { SevenZip } from "./7zip";
import { SystemPath } from "./system-path";
import { HydraApi } from "./hydra-api";
interface DeckyReleaseInfo {
version: string;
downloadUrl: string;
}
export class DeckyPlugin {
private static readonly EXPECTED_VERSION = "0.0.3";
private static readonly DOWNLOAD_URL =
"https://github.com/hydralauncher/decky-hydra-launcher/releases/download/0.0.3/Hydra.zip";
private static releaseInfo: DeckyReleaseInfo | null = null;
private static async getDeckyReleaseInfo(): Promise<DeckyReleaseInfo> {
if (this.releaseInfo) {
return this.releaseInfo;
}
try {
const response = await HydraApi.get<DeckyReleaseInfo>(
"/decky/release",
{},
{ needsAuth: false }
);
this.releaseInfo = response;
return response;
} catch (error) {
logger.error("Failed to fetch Decky release info:", error);
throw error;
}
}
private static getPackageJsonPath(): string {
return path.join(HYDRA_DECKY_PLUGIN_LOCATION, "package.json");
@@ -24,10 +48,11 @@ export class DeckyPlugin {
private static async downloadPlugin(): Promise<string> {
logger.log("Downloading Hydra Decky plugin...");
const releaseInfo = await this.getDeckyReleaseInfo();
const tempDir = SystemPath.getPath("temp");
const zipPath = path.join(tempDir, "Hydra.zip");
const response = await axios.get(this.DOWNLOAD_URL, {
const response = await axios.get(releaseInfo.downloadUrl, {
responseType: "arraybuffer",
});
@@ -209,14 +234,15 @@ export class DeckyPlugin {
return;
}
const releaseInfo = await this.getDeckyReleaseInfo();
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonContent);
const currentVersion = packageJson.version;
const isOutdated = currentVersion !== this.EXPECTED_VERSION;
const isOutdated = currentVersion !== releaseInfo.version;
if (isOutdated) {
logger.log(
`Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}. Updating...`
`Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}. Updating...`
);
await this.updatePlugin();
@@ -235,78 +261,139 @@ export class DeckyPlugin {
currentVersion: string | null;
expectedVersion: string;
}> {
if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
logger.log("Hydra Decky plugin folder not found, installing...");
try {
const releaseInfo = await this.getDeckyReleaseInfo();
if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
logger.log("Hydra Decky plugin folder not found, installing...");
try {
await this.updatePlugin();
// Read the actual installed version from package.json
const packageJsonPath = this.getPackageJsonPath();
if (fs.existsSync(packageJsonPath)) {
const packageJsonContent = fs.readFileSync(
packageJsonPath,
"utf-8"
);
const packageJson = JSON.parse(packageJsonContent);
return {
exists: true,
outdated: false,
currentVersion: packageJson.version,
expectedVersion: releaseInfo.version,
};
}
return {
exists: true,
outdated: false,
currentVersion: releaseInfo.version,
expectedVersion: releaseInfo.version,
};
} catch (error) {
logger.error("Failed to install plugin:", error);
return {
exists: false,
outdated: true,
currentVersion: null,
expectedVersion: releaseInfo.version,
};
}
}
const packageJsonPath = this.getPackageJsonPath();
try {
await this.updatePlugin();
if (!fs.existsSync(packageJsonPath)) {
logger.log(
"Hydra Decky plugin package.json not found, installing..."
);
await this.updatePlugin();
// Read the actual installed version from package.json
if (fs.existsSync(packageJsonPath)) {
const packageJsonContent = fs.readFileSync(
packageJsonPath,
"utf-8"
);
const packageJson = JSON.parse(packageJsonContent);
return {
exists: true,
outdated: false,
currentVersion: packageJson.version,
expectedVersion: releaseInfo.version,
};
}
return {
exists: true,
outdated: false,
currentVersion: releaseInfo.version,
expectedVersion: releaseInfo.version,
};
}
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonContent);
const currentVersion = packageJson.version;
const isOutdated = currentVersion !== releaseInfo.version;
if (isOutdated) {
logger.log(
`Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}`
);
await this.updatePlugin();
if (fs.existsSync(packageJsonPath)) {
const updatedPackageJsonContent = fs.readFileSync(
packageJsonPath,
"utf-8"
);
const updatedPackageJson = JSON.parse(updatedPackageJsonContent);
return {
exists: true,
outdated: false,
currentVersion: updatedPackageJson.version,
expectedVersion: releaseInfo.version,
};
}
return {
exists: true,
outdated: false,
currentVersion: releaseInfo.version,
expectedVersion: releaseInfo.version,
};
} else {
logger.log(`Hydra Decky plugin is up to date (${currentVersion})`);
}
return {
exists: true,
outdated: false,
currentVersion: this.EXPECTED_VERSION,
expectedVersion: this.EXPECTED_VERSION,
outdated: isOutdated,
currentVersion,
expectedVersion: releaseInfo.version,
};
} catch (error) {
logger.error("Failed to install plugin:", error);
logger.error(`Error checking Hydra Decky plugin version: ${error}`);
return {
exists: false,
outdated: true,
currentVersion: null,
expectedVersion: this.EXPECTED_VERSION,
expectedVersion: releaseInfo.version,
};
}
}
const packageJsonPath = this.getPackageJsonPath();
try {
if (!fs.existsSync(packageJsonPath)) {
logger.log("Hydra Decky plugin package.json not found, installing...");
await this.updatePlugin();
return {
exists: true,
outdated: false,
currentVersion: this.EXPECTED_VERSION,
expectedVersion: this.EXPECTED_VERSION,
};
}
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonContent);
const currentVersion = packageJson.version;
const isOutdated = currentVersion !== this.EXPECTED_VERSION;
if (isOutdated) {
logger.log(
`Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}`
);
await this.updatePlugin();
return {
exists: true,
outdated: false,
currentVersion: this.EXPECTED_VERSION,
expectedVersion: this.EXPECTED_VERSION,
};
} else {
logger.log(`Hydra Decky plugin is up to date (${currentVersion})`);
}
return {
exists: true,
outdated: isOutdated,
currentVersion,
expectedVersion: this.EXPECTED_VERSION,
};
} catch (error) {
logger.error(`Error checking Hydra Decky plugin version: ${error}`);
logger.error(`Error fetching release info: ${error}`);
return {
exists: false,
outdated: true,
currentVersion: null,
expectedVersion: this.EXPECTED_VERSION,
expectedVersion: "unknown",
};
}
}

View File

@@ -51,7 +51,8 @@ export function Sidebar() {
const [deckyPluginInfo, setDeckyPluginInfo] = useState<{
installed: boolean;
version: string | null;
}>({ installed: false, version: null });
outdated: boolean;
}>({ installed: false, version: null, outdated: false });
const [homebrewFolderExists, setHomebrewFolderExists] = useState(false);
const [showDeckyConfirmModal, setShowDeckyConfirmModal] = useState(false);
const navigate = useNavigate();
@@ -102,6 +103,7 @@ export function Sidebar() {
setDeckyPluginInfo({
installed: info.installed,
version: info.version,
outdated: info.outdated,
});
setHomebrewFolderExists(folderExists);
} catch (error) {
@@ -110,6 +112,9 @@ export function Sidebar() {
};
const handleInstallHydraDeckyPlugin = () => {
if (deckyPluginInfo.installed && !deckyPluginInfo.outdated) {
return;
}
setShowDeckyConfirmModal(true);
};
@@ -318,11 +323,13 @@ export function Sidebar() {
style={{ width: 16, height: 16 }}
/>
<span>
{deckyPluginInfo.installed
{deckyPluginInfo.installed && !deckyPluginInfo.outdated
? t("decky_plugin_installed_version", {
version: deckyPluginInfo.version,
})
: t("install_decky_plugin")}
: deckyPluginInfo.installed && deckyPluginInfo.outdated
? t("update_decky_plugin")
: t("install_decky_plugin")}
</span>
</button>
</li>
@@ -433,12 +440,12 @@ export function Sidebar() {
<ConfirmationModal
visible={showDeckyConfirmModal}
title={
deckyPluginInfo.installed
deckyPluginInfo.installed && deckyPluginInfo.outdated
? t("update_decky_plugin_title")
: t("install_decky_plugin_title")
}
descriptionText={
deckyPluginInfo.installed
deckyPluginInfo.installed && deckyPluginInfo.outdated
? t("update_decky_plugin_message")
: t("install_decky_plugin_message")
}

View File

@@ -350,6 +350,8 @@ declare global {
installed: boolean;
version: string | null;
path: string;
outdated: boolean;
expectedVersion: string | null;
}>;
checkHomebrewFolderExists: () => Promise<boolean>;
onCommonRedistProgress: (

View File

@@ -6,8 +6,6 @@ function removeZalgoText(text: string): string {
return text.replaceAll(zalgoRegex, "");
}
export function sanitizeHtml(html: string): string {
if (!html || typeof html !== "string") {
return "";