mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: removing crypto from level
This commit is contained in:
parent
47e6d88dd9
commit
0f0e27e2e5
12 changed files with 88 additions and 134 deletions
|
@ -3,7 +3,6 @@ import jwt from "jsonwebtoken";
|
|||
import { registerEvent } from "../register-event";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { Auth } from "@types";
|
||||
import { Crypto } from "@main/services";
|
||||
|
||||
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const auth = await db.get<string, Auth>(levelKeys.auth, {
|
||||
|
@ -11,9 +10,7 @@ const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
|||
});
|
||||
|
||||
if (!auth) return null;
|
||||
const payload = jwt.decode(
|
||||
Crypto.decrypt(auth.accessToken)
|
||||
) as jwt.JwtPayload;
|
||||
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;
|
||||
|
||||
if (!payload) return null;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { shell } from "electron";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { Crypto, HydraApi } from "@main/services";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { Auth } from "@types";
|
||||
|
||||
|
@ -14,7 +14,7 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
|
|||
}
|
||||
|
||||
const paymentToken = await HydraApi.post("/auth/payment", {
|
||||
refreshToken: Crypto.decrypt(auth.refreshToken),
|
||||
refreshToken: auth.refreshToken,
|
||||
}).then((response) => response.accessToken);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
|
|
|
@ -1,21 +1,10 @@
|
|||
import { registerEvent } from "../register-event";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import { Crypto } from "@main/services";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
const getUserPreferences = async () =>
|
||||
db
|
||||
.get<string, UserPreferences | null>(levelKeys.userPreferences, {
|
||||
valueEncoding: "json",
|
||||
})
|
||||
.then((userPreferences) => {
|
||||
if (userPreferences?.realDebridApiToken) {
|
||||
userPreferences.realDebridApiToken = Crypto.decrypt(
|
||||
userPreferences.realDebridApiToken
|
||||
);
|
||||
}
|
||||
|
||||
return userPreferences;
|
||||
});
|
||||
db.get<string, UserPreferences | null>(levelKeys.userPreferences, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
registerEvent("getUserPreferences", getUserPreferences);
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
Crypto,
|
||||
DownloadManager,
|
||||
logger,
|
||||
Ludusavi,
|
||||
startMainLoop,
|
||||
} from "./services";
|
||||
import { DownloadManager, logger, Ludusavi, startMainLoop } from "./services";
|
||||
import { RealDebridClient } from "./services/download/real-debrid";
|
||||
import { HydraApi } from "./services/hydra-api";
|
||||
import { uploadGamesBatch } from "./services/library-sync";
|
||||
|
@ -27,9 +21,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||
Aria2.spawn();
|
||||
|
||||
if (userPreferences?.realDebridApiToken) {
|
||||
RealDebridClient.authorize(
|
||||
Crypto.decrypt(userPreferences.realDebridApiToken)
|
||||
);
|
||||
RealDebridClient.authorize(userPreferences.realDebridApiToken);
|
||||
}
|
||||
|
||||
Ludusavi.addManifestToLudusaviConfig();
|
||||
|
@ -106,9 +98,7 @@ const migrateFromSqlite = async () => {
|
|||
|
||||
await db.put(levelKeys.userPreferences, {
|
||||
...rest,
|
||||
realDebridApiToken: realDebridApiToken
|
||||
? Crypto.encrypt(realDebridApiToken)
|
||||
: null,
|
||||
realDebridApiToken,
|
||||
preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1,
|
||||
runAtStartup: rest.runAtStartup === 1,
|
||||
startMinimized: rest.startMinimized === 1,
|
||||
|
@ -171,8 +161,8 @@ const migrateFromSqlite = async () => {
|
|||
await db.put<string, Auth>(
|
||||
levelKeys.auth,
|
||||
{
|
||||
accessToken: Crypto.encrypt(users[0].accessToken),
|
||||
refreshToken: Crypto.encrypt(users[0].refreshToken),
|
||||
accessToken: users[0].accessToken,
|
||||
refreshToken: users[0].refreshToken,
|
||||
tokenExpirationTimestamp: users[0].tokenExpirationTimestamp,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import { safeStorage } from "electron";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export class Crypto {
|
||||
public static encrypt(str: string) {
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
return safeStorage.encryptString(str).toString("base64");
|
||||
} else {
|
||||
logger.warn(
|
||||
"Encrypt method returned raw string because encryption is not available"
|
||||
);
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
public static decrypt(b64: string) {
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
return safeStorage.decryptString(Buffer.from(b64, "base64"));
|
||||
} else {
|
||||
logger.warn(
|
||||
"Decrypt method returned raw string because encryption is not available"
|
||||
);
|
||||
|
||||
return b64;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import { isFuture, isToday } from "date-fns";
|
|||
import { db } from "@main/level";
|
||||
import { levelKeys } from "@main/level/sublevels";
|
||||
import type { Auth, User } from "@types";
|
||||
import { Crypto } from "./crypto";
|
||||
|
||||
interface HydraApiOptions {
|
||||
needsAuth?: boolean;
|
||||
|
@ -32,7 +31,9 @@ export class HydraApi {
|
|||
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes
|
||||
private static readonly ADD_LOG_INTERCEPTOR = true;
|
||||
|
||||
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
|
||||
private static secondsToMilliseconds(seconds: number) {
|
||||
return seconds * 1000;
|
||||
}
|
||||
|
||||
private static userAuth: HydraApiUserAuth = {
|
||||
authToken: "",
|
||||
|
@ -80,8 +81,8 @@ export class HydraApi {
|
|||
db.put<string, Auth>(
|
||||
levelKeys.auth,
|
||||
{
|
||||
accessToken: Crypto.encrypt(accessToken),
|
||||
refreshToken: Crypto.encrypt(refreshToken),
|
||||
accessToken,
|
||||
refreshToken,
|
||||
tokenExpirationTimestamp,
|
||||
},
|
||||
{ valueEncoding: "json" }
|
||||
|
@ -194,12 +195,8 @@ export class HydraApi {
|
|||
const user = result.at(1) as User | undefined;
|
||||
|
||||
this.userAuth = {
|
||||
authToken: userAuth?.accessToken
|
||||
? Crypto.decrypt(userAuth.accessToken)
|
||||
: "",
|
||||
refreshToken: userAuth?.refreshToken
|
||||
? Crypto.decrypt(userAuth.refreshToken)
|
||||
: "",
|
||||
authToken: userAuth?.accessToken ?? "",
|
||||
refreshToken: userAuth?.refreshToken ?? "",
|
||||
expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0,
|
||||
subscription: user?.subscription
|
||||
? { expiresAt: user.subscription?.expiresAt }
|
||||
|
@ -248,7 +245,7 @@ export class HydraApi {
|
|||
levelKeys.auth,
|
||||
{
|
||||
...auth,
|
||||
accessToken: Crypto.encrypt(accessToken),
|
||||
accessToken,
|
||||
tokenExpirationTimestamp,
|
||||
},
|
||||
{ valueEncoding: "json" }
|
||||
|
|
|
@ -234,7 +234,7 @@ export class WindowManager {
|
|||
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
editorWindow.loadURL(
|
||||
`${process.env["ELECTRON_RENDERER_URL"]}#/editor?themeId=${themeId}`
|
||||
`${process.env["ELECTRON_RENDERER_URL"]}#/theme-editor?themeId=${themeId}`
|
||||
);
|
||||
} else {
|
||||
editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {
|
||||
|
|
|
@ -30,7 +30,6 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m
|
|||
|
||||
import { injectCustomCss } from "./helpers";
|
||||
import "./app.scss";
|
||||
import { Theme } from "@types";
|
||||
|
||||
export interface AppProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -214,22 +213,22 @@ export function App() {
|
|||
const id = crypto.randomUUID();
|
||||
const channel = new BroadcastChannel(`download_sources:sync:${id}`);
|
||||
|
||||
channel.onmessage = (event: MessageEvent<number>) => {
|
||||
channel.onmessage = async (event: MessageEvent<number>) => {
|
||||
const newRepacksCount = event.data;
|
||||
window.electron.publishNewRepacksNotification(newRepacksCount);
|
||||
updateRepacks();
|
||||
|
||||
downloadSourcesTable.toArray().then((downloadSources) => {
|
||||
downloadSources
|
||||
.filter((source) => !source.fingerprint)
|
||||
.forEach((downloadSource) => {
|
||||
window.electron
|
||||
.putDownloadSource(downloadSource.objectIds)
|
||||
.then(({ fingerprint }) => {
|
||||
downloadSourcesTable.update(downloadSource.id, { fingerprint });
|
||||
});
|
||||
});
|
||||
});
|
||||
const downloadSources = await downloadSourcesTable.toArray();
|
||||
|
||||
downloadSources
|
||||
.filter((source) => !source.fingerprint)
|
||||
.forEach(async (downloadSource) => {
|
||||
const { fingerprint } = await window.electron.putDownloadSource(
|
||||
downloadSource.objectIds
|
||||
);
|
||||
|
||||
downloadSourcesTable.update(downloadSource.id, { fingerprint });
|
||||
});
|
||||
};
|
||||
|
||||
downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]);
|
||||
|
@ -237,9 +236,9 @@ export function App() {
|
|||
|
||||
useEffect(() => {
|
||||
const loadAndApplyTheme = async () => {
|
||||
const activeTheme: Theme = await window.electron.getActiveCustomTheme();
|
||||
const activeTheme = await window.electron.getActiveCustomTheme();
|
||||
|
||||
if (activeTheme.code) {
|
||||
if (activeTheme?.code) {
|
||||
injectCustomCss(activeTheme.code);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -33,7 +33,9 @@ const Profile = React.lazy(() => import("./pages/profile/profile"));
|
|||
const Achievements = React.lazy(
|
||||
() => import("./pages/achievements/achievements")
|
||||
);
|
||||
const Editor = React.lazy(() => import("./pages/editor/editor"));
|
||||
const ThemeEditor = React.lazy(
|
||||
() => import("./pages/theme-editor/theme-editor")
|
||||
);
|
||||
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
|
@ -107,8 +109,8 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||
</Route>
|
||||
|
||||
<Route
|
||||
path="/editor"
|
||||
element={<SuspenseWrapper Component={Editor} />}
|
||||
path="/theme-editor"
|
||||
element={<SuspenseWrapper Component={ThemeEditor} />}
|
||||
/>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
|
|
|
@ -12,7 +12,7 @@ export function DeleteGameModal({
|
|||
onClose,
|
||||
visible,
|
||||
deleteGame,
|
||||
}: DeleteGameModalProps) {
|
||||
}: Readonly<DeleteGameModalProps>) {
|
||||
const { t } = useTranslation("downloads");
|
||||
|
||||
const handleDeleteGame = () => {
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
@use "../../scss/globals.scss" as globals;
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.editor {
|
||||
.theme-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&__header {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding: calc(globals.$spacing-unit * 2);
|
||||
background-color: globals.$dark-background-color;
|
||||
font-size: 8px;
|
||||
z-index: 50;
|
||||
-webkit-app-region: drag;
|
||||
gap: 8px;
|
||||
|
||||
&--darwin {
|
||||
padding-top: calc(globals.$spacing-unit * 6);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
line-height: 1;
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import "./editor.scss";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import "./theme-editor.scss";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { Theme } from "@types";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
@ -10,8 +10,9 @@ import {
|
|||
ProjectRoadmapIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import cn from "classnames";
|
||||
|
||||
const EditorPage = () => {
|
||||
export default function ThemeEditor() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [theme, setTheme] = useState<Theme | null>(null);
|
||||
const [code, setCode] = useState("");
|
||||
|
@ -37,29 +38,7 @@ const EditorPage = () => {
|
|||
}
|
||||
}, [themeId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
|
||||
event.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [code, theme]);
|
||||
|
||||
const handleEditorChange = (value: string | undefined) => {
|
||||
if (value !== undefined) {
|
||||
setCode(value);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const handleSave = useCallback(async () => {
|
||||
if (theme) {
|
||||
const updatedTheme = {
|
||||
...theme,
|
||||
|
@ -74,13 +53,41 @@ const EditorPage = () => {
|
|||
window.electron.injectCSS(code);
|
||||
}
|
||||
}
|
||||
}, [code, theme]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
|
||||
event.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [code, handleSave, theme]);
|
||||
|
||||
const handleEditorChange = (value: string | undefined) => {
|
||||
if (value !== undefined) {
|
||||
setCode(value);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="editor">
|
||||
<div className="editor__header">
|
||||
<div className="theme-editor">
|
||||
<div
|
||||
className={cn("theme-editor__header", {
|
||||
"theme-editor__header--darwin": window.electron.platform === "darwin",
|
||||
})}
|
||||
>
|
||||
<h1>{theme?.name}</h1>
|
||||
{hasUnsavedChanges && <div className="editor__header__status"></div>}
|
||||
{hasUnsavedChanges && (
|
||||
<div className="theme-editor__header__status"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{activeTab === "code" && (
|
||||
|
@ -100,15 +107,15 @@ const EditorPage = () => {
|
|||
)}
|
||||
|
||||
{activeTab === "info" && (
|
||||
<div className="editor__info">
|
||||
<div className="theme-editor__info">
|
||||
entao mano eu ate fiz isso aqui mas tava feio dms ai deu vergonha e
|
||||
removi kkkk
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="editor__footer">
|
||||
<div className="editor__footer-actions">
|
||||
<div className="editor__footer-actions__tabs">
|
||||
<div className="theme-editor__footer">
|
||||
<div className="theme-editor__footer-actions">
|
||||
<div className="theme-editor__footer-actions__tabs">
|
||||
<Button
|
||||
onClick={() => handleTabChange("code")}
|
||||
theme="dark"
|
||||
|
@ -135,6 +142,4 @@ const EditorPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorPage;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue