mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 05:46:17 +00:00
feat: adding ota updates
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.18.0
|
node-version: 20.18.3
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
run: yarn --frozen-lockfile
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.18.0
|
node-version: 20.18.3
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
run: yarn --frozen-lockfile
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -6,7 +6,7 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: main
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.18.0
|
node-version: 20.18.3
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
run: yarn --frozen-lockfile
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -32,13 +32,13 @@
|
|||||||
"protoc": "npx protoc --ts_out src/main/generated --proto_path proto proto/*.proto"
|
"protoc": "npx protoc --ts_out src/main/generated --proto_path proto proto/*.proto"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.0",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^3.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"@fontsource/noto-sans": "^5.1.0",
|
"@fontsource/noto-sans": "^5.2.10",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@primer/octicons-react": "^19.9.0",
|
"@primer/octicons-react": "^19.9.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
"@tiptap/extension-bold": "^3.6.2",
|
"@tiptap/extension-bold": "^3.6.2",
|
||||||
"@tiptap/extension-italic": "^3.6.2",
|
"@tiptap/extension-italic": "^3.6.2",
|
||||||
@@ -47,8 +47,9 @@
|
|||||||
"@tiptap/react": "^3.6.2",
|
"@tiptap/react": "^3.6.2",
|
||||||
"@tiptap/starter-kit": "^3.6.2",
|
"@tiptap/starter-kit": "^3.6.2",
|
||||||
"auto-launch": "^5.0.6",
|
"auto-launch": "^5.0.6",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.12.2",
|
||||||
"axios-cookiejar-support": "^5.0.5",
|
"axios-cookiejar-support": "^5.0.5",
|
||||||
|
"check-disk-space": "^3.4.0",
|
||||||
"classic-level": "^2.0.0",
|
"classic-level": "^2.0.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
@@ -57,8 +58,7 @@
|
|||||||
"create-desktop-shortcuts": "^1.11.1",
|
"create-desktop-shortcuts": "^1.11.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"dexie": "^4.0.10",
|
"dexie": "^4.0.10",
|
||||||
"diskusage": "^1.2.0",
|
"electron-log": "^5.4.3",
|
||||||
"electron-log": "^5.2.4",
|
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"embla-carousel-autoplay": "^8.6.0",
|
"embla-carousel-autoplay": "^8.6.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"@types/winreg": "^1.2.36",
|
"@types/winreg": "^1.2.36",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"electron": "^32.3.3",
|
"electron": "^33.4.11",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-vite": "^3.0.0",
|
"electron-vite": "^3.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -131,8 +131,8 @@
|
|||||||
"sass-embedded": "^1.80.6",
|
"sass-embedded": "^1.80.6",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "5.4.20",
|
||||||
"vite-plugin-svgr": "^4.2.0"
|
"vite-plugin-svgr": "^4.5.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import disk from "diskusage";
|
import { DiskUsage } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
import checkDiskSpace from "check-disk-space";
|
||||||
|
|
||||||
const getDiskFreeSpace = async (
|
const getDiskFreeSpace = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
path: string
|
path: string
|
||||||
) => disk.check(path);
|
): Promise<DiskUsage> => {
|
||||||
|
const result = await checkDiskSpace(path);
|
||||||
|
return { free: result.free, total: result.size };
|
||||||
|
};
|
||||||
|
|
||||||
registerEvent("getDiskFreeSpace", getDiskFreeSpace);
|
registerEvent("getDiskFreeSpace", getDiskFreeSpace);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
clearGamesPlaytime,
|
clearGamesPlaytime,
|
||||||
WindowManager,
|
WindowManager,
|
||||||
Lock,
|
Lock,
|
||||||
|
Aria2,
|
||||||
} from "@main/services";
|
} from "@main/services";
|
||||||
import resources from "@locales";
|
import resources from "@locales";
|
||||||
import { PythonRPC } from "./services/python-rpc";
|
import { PythonRPC } from "./services/python-rpc";
|
||||||
@@ -222,6 +223,7 @@ app.on("before-quit", async (e) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
/* Disconnects libtorrent */
|
/* Disconnects libtorrent */
|
||||||
PythonRPC.kill();
|
PythonRPC.kill();
|
||||||
|
Aria2.kill();
|
||||||
await clearGamesPlaytime();
|
await clearGamesPlaytime();
|
||||||
canAppBeClosed = true;
|
canAppBeClosed = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import cp from "node:child_process";
|
import cp from "node:child_process";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
|
||||||
export class Aria2 {
|
export class Aria2 {
|
||||||
private static process: cp.ChildProcess | null = null;
|
private static process: cp.ChildProcess | null = null;
|
||||||
@@ -23,6 +24,9 @@ export class Aria2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static kill() {
|
public static kill() {
|
||||||
this.process?.kill();
|
if (this.process) {
|
||||||
|
logger.log("Killing aria2 process");
|
||||||
|
this.process.kill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,11 +79,18 @@ const findGamePathByProcess = async (
|
|||||||
const executables = gameExecutables[gameId];
|
const executables = gameExecutables[gameId];
|
||||||
|
|
||||||
for (const executable of executables) {
|
for (const executable of executables) {
|
||||||
const pathSet = processMap.get(executable.exe);
|
const executablewithoutExtension = executable.exe.replace(/\.exe$/i, "");
|
||||||
|
|
||||||
|
const pathSet =
|
||||||
|
processMap.get(executable.exe) ??
|
||||||
|
processMap.get(executablewithoutExtension);
|
||||||
|
|
||||||
if (pathSet) {
|
if (pathSet) {
|
||||||
for (const path of pathSet) {
|
for (const path of pathSet) {
|
||||||
if (path.toLowerCase().endsWith(executable.name)) {
|
if (
|
||||||
|
path.toLowerCase().endsWith(executable.name) ||
|
||||||
|
path.toLowerCase().endsWith(executablewithoutExtension)
|
||||||
|
) {
|
||||||
const gameKey = levelKeys.game("steam", gameId);
|
const gameKey = levelKeys.game("steam", gameId);
|
||||||
const game = await gamesSublevel.get(gameKey);
|
const game = await gamesSublevel.get(gameKey);
|
||||||
|
|
||||||
@@ -124,7 +131,6 @@ const getSystemProcessMap = async () => {
|
|||||||
if (!key || !value) return;
|
if (!key || !value) return;
|
||||||
|
|
||||||
const STEAM_COMPAT_DATA_PATH = process.environ?.STEAM_COMPAT_DATA_PATH;
|
const STEAM_COMPAT_DATA_PATH = process.environ?.STEAM_COMPAT_DATA_PATH;
|
||||||
|
|
||||||
if (STEAM_COMPAT_DATA_PATH) {
|
if (STEAM_COMPAT_DATA_PATH) {
|
||||||
winePrefixMap.set(value, STEAM_COMPAT_DATA_PATH);
|
winePrefixMap.set(value, STEAM_COMPAT_DATA_PATH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,58 +8,65 @@ import { levelKeys } from "@main/level/sublevels";
|
|||||||
export const getUserData = async () => {
|
export const getUserData = async () => {
|
||||||
return HydraApi.get<UserDetails>(`/profile/me`)
|
return HydraApi.get<UserDetails>(`/profile/me`)
|
||||||
.then(async (me) => {
|
.then(async (me) => {
|
||||||
db.get<string, User>(levelKeys.user, { valueEncoding: "json" }).then(
|
try {
|
||||||
(user) => {
|
const user = await db.get<string, User>(levelKeys.user, {
|
||||||
return db.put<string, User>(
|
valueEncoding: "json",
|
||||||
levelKeys.user,
|
});
|
||||||
{
|
await db.put<string, User>(
|
||||||
...user,
|
levelKeys.user,
|
||||||
id: me.id,
|
{
|
||||||
displayName: me.displayName,
|
...user,
|
||||||
profileImageUrl: me.profileImageUrl,
|
id: me.id,
|
||||||
backgroundImageUrl: me.backgroundImageUrl,
|
displayName: me.displayName,
|
||||||
subscription: me.subscription,
|
profileImageUrl: me.profileImageUrl,
|
||||||
},
|
backgroundImageUrl: me.backgroundImageUrl,
|
||||||
{ valueEncoding: "json" }
|
subscription: me.subscription,
|
||||||
);
|
},
|
||||||
}
|
{ valueEncoding: "json" }
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to update user in DB", error);
|
||||||
|
}
|
||||||
return me;
|
return me;
|
||||||
})
|
})
|
||||||
.catch(async (err) => {
|
.catch(async (err) => {
|
||||||
if (err instanceof UserNotLoggedInError) {
|
if (err instanceof UserNotLoggedInError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
logger.error("Failed to get logged user");
|
|
||||||
|
|
||||||
const loggedUser = await db.get<string, User>(levelKeys.user, {
|
logger.error("Failed to get logged user", err);
|
||||||
valueEncoding: "json",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loggedUser) {
|
try {
|
||||||
return {
|
const loggedUser = await db.get<string, User>(levelKeys.user, {
|
||||||
...loggedUser,
|
valueEncoding: "json",
|
||||||
username: "",
|
});
|
||||||
bio: "",
|
|
||||||
email: null,
|
if (loggedUser) {
|
||||||
profileVisibility: "PUBLIC" as ProfileVisibility,
|
return {
|
||||||
quirks: {
|
...loggedUser,
|
||||||
backupsPerGameLimit: 0,
|
username: "",
|
||||||
},
|
bio: "",
|
||||||
subscription: loggedUser.subscription
|
email: null,
|
||||||
? {
|
profileVisibility: "PUBLIC" as ProfileVisibility,
|
||||||
id: loggedUser.subscription.id,
|
quirks: {
|
||||||
status: loggedUser.subscription.status,
|
backupsPerGameLimit: 0,
|
||||||
plan: {
|
},
|
||||||
id: loggedUser.subscription.plan.id,
|
subscription: loggedUser.subscription
|
||||||
name: loggedUser.subscription.plan.name,
|
? {
|
||||||
},
|
id: loggedUser.subscription.id,
|
||||||
expiresAt: loggedUser.subscription.expiresAt,
|
status: loggedUser.subscription.status,
|
||||||
}
|
plan: {
|
||||||
: null,
|
id: loggedUser.subscription.plan.id,
|
||||||
featurebaseJwt: "",
|
name: loggedUser.subscription.plan.name,
|
||||||
} as UserDetails;
|
},
|
||||||
|
expiresAt: loggedUser.subscription.expiresAt,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
featurebaseJwt: "",
|
||||||
|
} as UserDetails;
|
||||||
|
}
|
||||||
|
} catch (dbError) {
|
||||||
|
logger.error("Failed to read user from DB", dbError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
4
src/renderer/src/declaration.d.ts
vendored
4
src/renderer/src/declaration.d.ts
vendored
@@ -30,9 +30,9 @@ import type {
|
|||||||
AchievementCustomNotificationPosition,
|
AchievementCustomNotificationPosition,
|
||||||
AchievementNotificationInfo,
|
AchievementNotificationInfo,
|
||||||
Game,
|
Game,
|
||||||
|
DiskUsage,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { AxiosProgressEvent } from "axios";
|
import type { AxiosProgressEvent } from "axios";
|
||||||
import type disk from "diskusage";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
declare module "*.svg" {
|
declare module "*.svg" {
|
||||||
@@ -220,7 +220,7 @@ declare global {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
/* Hardware */
|
/* Hardware */
|
||||||
getDiskFreeSpace: (path: string) => Promise<disk.DiskUsage>;
|
getDiskFreeSpace: (path: string) => Promise<DiskUsage>;
|
||||||
checkFolderWritePermission: (path: string) => Promise<boolean>;
|
checkFolderWritePermission: (path: string) => Promise<boolean>;
|
||||||
|
|
||||||
/* Cloud save */
|
/* Cloud save */
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export type HydraCloudFeature =
|
|||||||
| "backup"
|
| "backup"
|
||||||
| "achievements-points";
|
| "achievements-points";
|
||||||
|
|
||||||
|
export interface DiskUsage {
|
||||||
|
free: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameRepack {
|
export interface GameRepack {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user