Skip to content

Commit

Permalink
Merge branch 'main' into feature/delete-all-dowload-sources
Browse files Browse the repository at this point in the history
  • Loading branch information
KelvinDiasMoreira authored Jan 22, 2025
2 parents b322262 + dfc2dd1 commit c65fc15
Show file tree
Hide file tree
Showing 17 changed files with 451 additions and 207 deletions.
4 changes: 4 additions & 0 deletions docs/README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ cd hydra
yarn
```

### <a name="install-openssl-11"></a> Instale OpenSSL 1.1

[OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows.

### <a name="install-python-39"></a> Instale Python 3.9

Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/).
Expand Down
18 changes: 17 additions & 1 deletion src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,23 @@
"launch_minimized": "Launch Hydra minimized",
"disable_nsfw_alert": "Disable NSFW alert",
"seed_after_download_complete": "Seed after download complete",
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them"
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them",
"account": "Account",
"no_users_blocked": "You have no blocked users",
"subscription_active_until": "Your Hydra Cloud is active until {{date}}",
"manage_subscription": "Manage subscription",
"update_email": "Update email",
"update_password": "Update password",
"current_email": "Current email:",
"no_email_account": "You have not set an email yet",
"account_data_updated_successfully": "Account data updated successfully",
"renew_subscription": "Renew Hydra Cloud",
"subscription_expired_at": "Your subscription expired at {{date}}",
"no_subscription": "Enjoy Hydra in the best possible way",
"become_subscriber": "Be Hydra Cloud",
"subscription_renew_cancelled": "Automatic renewal is disabled",
"subscription_renews_on": "Your subscription renews on {{date}}",
"bill_sent_until": "Your next bill will be sent until this day"
},
"notifications": {
"download_complete": "Download complete",
Expand Down
20 changes: 18 additions & 2 deletions src/locales/pt-BR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,23 @@
"launch_minimized": "Iniciar o Hydra minimizado",
"disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado",
"seed_after_download_complete": "Semear após a conclusão do download",
"show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las"
"show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las",
"account": "Conta",
"no_users_blocked": "Você não bloqueou nenhum usuário",
"subscription_active_until": "Sua assinatura Hydra Cloud ficará ativa até {{date}}",
"manage_subscription": "Gerenciar assinatura",
"update_email": "Atualizar email",
"update_password": "Atualizar senha",
"current_email": "Email atual:",
"no_email_account": "Você ainda não adicionou um email a sua conta",
"account_data_updated_successfully": "Dados da conta atualizados com sucesso",
"renew_subscription": "Renovar Hydra Cloud",
"subscription_expired_at": "Sua assinatura expirou em {{date}}",
"no_subscription": "Aproveite o Hydra da melhor forma possível",
"become_subscriber": "Seja Hydra Cloud",
"subscription_renew_cancelled": "A renovação automática está desativada",
"subscription_renews_on": "Sua assinatura renova dia {{date}}",
"bill_sent_until": "Sua próxima cobrança será enviada até esse dia"
},
"notifications": {
"download_complete": "Download concluído",
Expand Down Expand Up @@ -403,7 +419,7 @@
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos",
"achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas",
"achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}",
"hidden_achievement_tooltip": "Está é uma conquista oculta",
"hidden_achievement_tooltip": "Esta é uma conquista oculta",
"achievement_earn_points": "Ganhe {{points}} pontos com essa conquista",
"earned_points": "Pontos ganhos:",
"available_points": "Pontos disponíveis:",
Expand Down
18 changes: 17 additions & 1 deletion src/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,23 @@
"source_already_exists": "Этот источник уже добавлен",
"user_unblocked": "Пользователь разблокирован",
"seed_after_download_complete": "Раздавать после завершения загрузки",
"show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением"
"show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением",
"account": "Аккаунт",
"no_users_blocked": "У вас нет заблокированных пользователей",
"subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}",
"manage_subscription": "Управлять подпиской",
"update_email": "Обновить электронную почту",
"update_password": "Обновить пароль",
"current_email": "Текущий email:",
"no_email_account": "Вы еще не установили электронную почту",
"account_data_updated_successfully": "Данные учетной записи успешно обновлены",
"renew_subscription": "Обновить подписку Hydra Cloud",
"subscription_expired_at": "Срок действия вашей подписки истек в {{date}}",
"no_subscription": "Наслаждайтесь Hydra по максимуму",
"become_subscriber": "Станьте обладателем Hydra Cloud",
"subscription_renew_cancelled": "Автоматическое продление отключено",
"subscription_renews_on": "Ваша подписка продлевается на {{date}}",
"bill_sent_until": "Ваш следующий счет будет отправлен до этого дня"
},
"notifications": {
"download_complete": "Загрузка завершена",
Expand Down
23 changes: 20 additions & 3 deletions src/main/events/auth/open-auth-window.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import i18next from "i18next";
import { registerEvent } from "../register-event";
import { WindowManager } from "@main/services";
import { HydraApi, WindowManager } from "@main/services";
import { AuthPage } from "@shared";

const openAuthWindow = async (_event: Electron.IpcMainInvokeEvent) =>
WindowManager.openAuthWindow();
const openAuthWindow = async (
_event: Electron.IpcMainInvokeEvent,
page: AuthPage
) => {
const searchParams = new URLSearchParams({
lng: i18next.language,
});

if ([AuthPage.UpdateEmail, AuthPage.UpdatePassword].includes(page)) {
const { accessToken } = await HydraApi.refreshToken().catch(() => {
return { accessToken: "" };
});
searchParams.set("token", accessToken);
}

WindowManager.openAuthWindow(page, searchParams);
};

registerEvent("openAuthWindow", openAuthWindow);
66 changes: 35 additions & 31 deletions src/main/services/hydra-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,38 +215,42 @@ export class HydraApi {
}
}

private static async revalidateAccessTokenIfExpired() {
const now = new Date();
public static async refreshToken() {
const { accessToken, expiresIn } = await this.instance
.post<{ accessToken: string; expiresIn: number }>(`/auth/refresh`, {
refreshToken: this.userAuth.refreshToken,
})
.then((response) => response.data);

const tokenExpirationTimestamp =
Date.now() +
this.secondsToMilliseconds(expiresIn) -
this.EXPIRATION_OFFSET_IN_MS;

if (this.userAuth.expirationTimestamp < now.getTime()) {
this.userAuth.authToken = accessToken;
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;

logger.log(
"Token refreshed. New expiration:",
this.userAuth.expirationTimestamp
);

userAuthRepository.upsert(
{
id: 1,
accessToken,
tokenExpirationTimestamp,
},
["id"]
);

return { accessToken, expiresIn };
}

private static async revalidateAccessTokenIfExpired() {
if (this.userAuth.expirationTimestamp < Date.now()) {
try {
const response = await this.instance.post(`/auth/refresh`, {
refreshToken: this.userAuth.refreshToken,
});

const { accessToken, expiresIn } = response.data;

const tokenExpirationTimestamp =
now.getTime() +
this.secondsToMilliseconds(expiresIn) -
this.EXPIRATION_OFFSET_IN_MS;

this.userAuth.authToken = accessToken;
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;

logger.log(
"Token refreshed. New expiration:",
this.userAuth.expirationTimestamp
);

userAuthRepository.upsert(
{
id: 1,
accessToken,
tokenExpirationTimestamp,
},
["id"]
);
await this.refreshToken();
} catch (err) {
this.handleUnauthorizedError(err);
}
Expand All @@ -261,7 +265,7 @@ export class HydraApi {
};
}

private static handleUnauthorizedError = (err) => {
private static readonly handleUnauthorizedError = (err) => {
if (err instanceof AxiosError && err.response?.status === 401) {
logger.error(
"401 - Current credentials:",
Expand Down
18 changes: 11 additions & 7 deletions src/main/services/window-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
shell,
} from "electron";
import { is } from "@electron-toolkit/utils";
import i18next, { t } from "i18next";
import { t } from "i18next";
import path from "node:path";
import icon from "@resources/icon.png?asset";
import trayIcon from "@resources/tray-icon.png?asset";
import { gameRepository, userPreferencesRepository } from "@main/repository";
import { IsNull, Not } from "typeorm";
import { HydraApi } from "./hydra-api";
import UserAgent from "user-agents";
import { AuthPage } from "@shared";

export class WindowManager {
public static mainWindow: Electron.BrowserWindow | null = null;
Expand Down Expand Up @@ -142,7 +143,7 @@ export class WindowManager {
});
}

public static openAuthWindow() {
public static openAuthWindow(page: AuthPage, searchParams: URLSearchParams) {
if (this.mainWindow) {
const authWindow = new BrowserWindow({
width: 600,
Expand All @@ -164,12 +165,8 @@ export class WindowManager {

if (!app.isPackaged) authWindow.webContents.openDevTools();

const searchParams = new URLSearchParams({
lng: i18next.language,
});

authWindow.loadURL(
`${import.meta.env.MAIN_VITE_AUTH_URL}/?${searchParams.toString()}`
`${import.meta.env.MAIN_VITE_AUTH_URL}${page}?${searchParams.toString()}`
);

authWindow.once("ready-to-show", () => {
Expand All @@ -181,6 +178,13 @@ export class WindowManager {
authWindow.close();

HydraApi.handleExternalAuth(url);
return;
}

if (url.startsWith("hydralauncher://update-account")) {
authWindow.close();

WindowManager.mainWindow?.webContents.send("on-account-updated");
}
});
}
Expand Down
10 changes: 8 additions & 2 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
SeedingStatus,
GameAchievement,
} from "@types";
import type { CatalogueCategory } from "@shared";
import type { AuthPage, CatalogueCategory } from "@shared";
import type { AxiosProgressEvent } from "axios";

contextBridge.exposeInMainWorld("electron", {
Expand Down Expand Up @@ -291,13 +291,19 @@ contextBridge.exposeInMainWorld("electron", {

/* Auth */
signOut: () => ipcRenderer.invoke("signOut"),
openAuthWindow: () => ipcRenderer.invoke("openAuthWindow"),
openAuthWindow: (page: AuthPage) =>
ipcRenderer.invoke("openAuthWindow", page),
getSessionHash: () => ipcRenderer.invoke("getSessionHash"),
onSignIn: (cb: () => void) => {
const listener = (_event: Electron.IpcRendererEvent) => cb();
ipcRenderer.on("on-signin", listener);
return () => ipcRenderer.removeListener("on-signin", listener);
},
onAccountUpdated: (cb: () => void) => {
const listener = (_event: Electron.IpcRendererEvent) => cb();
ipcRenderer.on("on-account-updated", listener);
return () => ipcRenderer.removeListener("on-account-updated", listener);
},
onSignOut: (cb: () => void) => {
const listener = (_event: Electron.IpcRendererEvent) => cb();
ipcRenderer.on("on-signout", listener);
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/src/components/sidebar/sidebar-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
import { Avatar } from "../avatar/avatar";
import { AuthPage } from "@shared";

const LONG_POLLING_INTERVAL = 120_000;

Expand All @@ -26,11 +27,11 @@ export function SidebarProfile() {

const handleProfileClick = () => {
if (userDetails === null) {
window.electron.openAuthWindow();
window.electron.openAuthWindow(AuthPage.SignIn);
return;
}

navigate(`/profile/${userDetails!.id}`);
navigate(`/profile/${userDetails.id}`);
};

useEffect(() => {
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/src/declaration.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CatalogueCategory } from "@shared";
import type { AuthPage, CatalogueCategory } from "@shared";
import type {
AppUpdaterEvent,
Game,
Expand Down Expand Up @@ -208,9 +208,10 @@ declare global {

/* Auth */
signOut: () => Promise<void>;
openAuthWindow: () => Promise<void>;
openAuthWindow: (page: AuthPage) => Promise<void>;
getSessionHash: () => Promise<string | null>;
onSignIn: (cb: () => void) => () => Electron.IpcRenderer;
onAccountUpdated: (cb: () => void) => () => Electron.IpcRenderer;
onSignOut: (cb: () => void) => () => Electron.IpcRenderer;

/* User */
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/pages/game-details/game-details-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Sidebar } from "./sidebar/sidebar";
import * as styles from "./game-details.css";
import { useTranslation } from "react-i18next";
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
import { steamUrlBuilder } from "@shared";
import { AuthPage, steamUrlBuilder } from "@shared";

import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif";
import { useUserDetails } from "@renderer/hooks";
Expand Down Expand Up @@ -69,7 +69,7 @@ export function GameDetailsContent() {
});

const backgroundColor = output
? (new Color(output).darken(0.7).toString() as string)
? new Color(output).darken(0.7).toString()
: "";

setGameColor(backgroundColor);
Expand Down Expand Up @@ -101,7 +101,7 @@ export function GameDetailsContent() {

const handleCloudSaveButtonClick = () => {
if (!userDetails) {
window.electron.openAuthWindow();
window.electron.openAuthWindow(AuthPage.SignIn);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChevronDownIcon } from "@primer/octicons-react";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";

import * as styles from "./sidebar-section.css";

Expand All @@ -11,6 +11,15 @@ export interface SidebarSectionProps {
export function SidebarSection({ title, children }: SidebarSectionProps) {
const content = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(true);
const [height, setHeight] = useState(0);

useEffect(() => {
if (content.current && content.current.scrollHeight !== height) {
setHeight(isOpen ? content.current.scrollHeight : 0);
} else if (!isOpen) {
setHeight(0);
}
}, [isOpen, children, height]);

return (
<div>
Expand All @@ -26,7 +35,7 @@ export function SidebarSection({ title, children }: SidebarSectionProps) {
<div
ref={content}
style={{
maxHeight: isOpen ? `${content.current?.scrollHeight}px` : "0",
maxHeight: `${height}px`,
overflow: "hidden",
transition: "max-height 0.4s cubic-bezier(0, 1, 0, 1)",
position: "relative",
Expand Down
Loading

0 comments on commit c65fc15

Please sign in to comment.