Merge branch 'main' into feature/cloud-sync-improvements

This commit is contained in:
Zamitto 2024-10-22 17:32:55 -03:00 committed by GitHub
commit 034e71b3ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 846 additions and 94 deletions

View file

@ -7,7 +7,7 @@ import {
gamesPlaytime,
} from "@main/services";
import { dataSource } from "@main/data-source";
import { DownloadQueue, Game, UserAuth } from "@main/entity";
import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity";
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
const databaseOperations = dataSource
@ -19,6 +19,10 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
await transactionalEntityManager
.getRepository(UserAuth)
.delete({ id: 1 });
await transactionalEntityManager
.getRepository(UserSubscription)
.delete({ id: 1 });
})
.then(() => {
/* Removes all games being played */

View file

@ -1,43 +1,11 @@
import { registerEvent } from "../register-event";
import { logger } from "@main/services";
import type { ProfileVisibility, UserDetails } from "@types";
import { userAuthRepository } from "@main/repository";
import { UserNotLoggedInError } from "@shared";
import type { UserDetails } from "@types";
import { getUserData } from "@main/services/user/get-user-data";
const getMe = async (
_event: Electron.IpcMainInvokeEvent
): Promise<UserDetails | null> => {
return getUserData().catch(async (err) => {
if (err instanceof UserNotLoggedInError) {
return null;
}
logger.error("Failed to get logged user", err);
const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } });
if (loggedUser) {
return {
...loggedUser,
id: loggedUser.userId,
username: "",
bio: "",
profileVisibility: "PUBLIC" as ProfileVisibility,
subscription: loggedUser.subscription
? {
id: loggedUser.subscription.subscriptionId,
status: loggedUser.subscription.status,
plan: {
id: loggedUser.subscription.planId,
name: loggedUser.subscription.planName,
},
expiresAt: loggedUser.subscription.expiresAt,
}
: null,
};
}
return null;
});
return getUserData();
};
registerEvent("getMe", getMe);

View file

@ -12,7 +12,6 @@ import { UserPreferences } from "./entity";
import { RealDebridClient } from "./services/real-debrid";
import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync";
import { getUserData } from "./services/user/get-user-data";
const loadState = async (userPreferences: UserPreferences | null) => {
import("./events");
@ -23,8 +22,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
Ludusavi.addManifestToLudusaviConfig();
await HydraApi.setupApi().then(async () => {
await getUserData().catch(() => {});
HydraApi.setupApi().then(() => {
uploadGamesBatch();
});

View file

@ -71,6 +71,7 @@ const crackers = [
Cracker.smartSteamEmu,
Cracker.empress,
Cracker.flt,
Cracker.razor1911,
];
const getPathFromCracker = (cracker: Cracker) => {
@ -221,6 +222,15 @@ const getPathFromCracker = (cracker: Cracker) => {
];
}
if (cracker == Cracker.razor1911) {
return [
{
folderPath: path.join(appData, ".1911"),
fileLocation: ["achievement"],
},
];
}
achievementsLogger.error(`Cracker ${cracker} not implemented`);
throw new Error(`Cracker ${cracker} not implemented`);
};

View file

@ -125,7 +125,7 @@ export const mergeAchievements = async (
id: game.remoteId,
achievements: mergedLocalAchievements,
},
{ needsCloud: true }
{ needsSubscription: true }
)
.then((response) => {
return saveAchievementsOnLocal(

View file

@ -65,6 +65,10 @@ export const parseAchievementFile = (
return processCreamAPI(parsed);
}
if (type === Cracker.razor1911) {
return processRazor1911(filePath);
}
achievementsLogger.log(
`Unprocessed ${type} achievements found on ${filePath}`
);
@ -111,6 +115,35 @@ const jsonParse = (filePath: string) => {
}
};
const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
try {
const fileContent = readFileSync(filePath, "utf-8");
achievementsLogger.log("processing file", filePath, fileContent);
const lines =
fileContent.charCodeAt(0) === 0xfeff
? fileContent.slice(1).split(/[\r\n]+/)
: fileContent.split(/[\r\n]+/);
const achievements: UnlockedAchievement[] = [];
for (const line of lines) {
if (!line.length) continue;
const [name, unlocked, unlockTime] = line.split(" ");
if (unlocked === "1") {
achievements.push({
name,
unlockTime: Number(unlockTime) * 1000,
});
}
}
achievementsLogger.log("processing file", achievements);
return achievements;
} catch (err) {
achievementsLogger.error(`Error processing ${filePath}`, err);
return [];
}
};
const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {
const parsedUnlockedAchievements: UnlockedAchievement[] = [];

View file

@ -11,10 +11,18 @@ import { logger } from "./logger";
import { UserNotLoggedInError, SubscriptionRequiredError } from "@shared";
import { omit } from "lodash-es";
import { appVersion } from "@main/constants";
import { getUserData } from "./user/get-user-data";
interface HydraApiOptions {
needsAuth?: boolean;
needsCloud?: boolean;
needsSubscription?: boolean;
}
interface HydraApiUserAuth {
authToken: string;
refreshToken: string;
expirationTimestamp: number;
subscription: { expiresAt: Date | null } | null;
}
export class HydraApi {
@ -25,27 +33,22 @@ export class HydraApi {
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
private static userAuth = {
private static userAuth: HydraApiUserAuth = {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
subscription: null,
};
private static isLoggedIn() {
return this.userAuth.authToken !== "";
}
private static async hasCloudSubscription() {
return userSubscriptionRepository
.findOne({ where: { id: 1 } })
.then((userSubscription) => {
if (!userSubscription) return false;
return (
!userSubscription.expiresAt ||
userSubscription!.expiresAt > new Date()
);
});
private static hasCloudSubscription() {
return (
this.userAuth.subscription?.expiresAt &&
this.userAuth.subscription.expiresAt > new Date()
);
}
static async handleExternalAuth(uri: string) {
@ -67,6 +70,7 @@ export class HydraApi {
authToken: accessToken,
refreshToken: refreshToken,
expirationTimestamp: tokenExpirationTimestamp,
subscription: null,
};
logger.log(
@ -84,6 +88,16 @@ export class HydraApi {
["id"]
);
await getUserData().then((userDetails) => {
if (userDetails?.subscription) {
this.userAuth.subscription = {
expiresAt: userDetails.subscription.expiresAt
? new Date(userDetails.subscription.expiresAt)
: null,
};
}
});
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-signin");
await clearGamesRemoteIds();
@ -96,6 +110,7 @@ export class HydraApi {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
subscription: null,
};
}
@ -161,14 +176,20 @@ export class HydraApi {
);
}
await getUserData();
const userAuth = await userAuthRepository.findOne({
where: { id: 1 },
relations: { subscription: true },
});
this.userAuth = {
authToken: userAuth?.accessToken ?? "",
refreshToken: userAuth?.refreshToken ?? "",
expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0,
subscription: userAuth?.subscription
? { expiresAt: userAuth.subscription?.expiresAt }
: null,
};
}
@ -236,9 +257,11 @@ export class HydraApi {
authToken: "",
expirationTimestamp: 0,
refreshToken: "",
subscription: null,
};
userAuthRepository.delete({ id: 1 });
userSubscriptionRepository.delete({ id: 1 });
this.sendSignOutEvent();
}
@ -248,14 +271,14 @@ export class HydraApi {
private static async validateOptions(options?: HydraApiOptions) {
const needsAuth = options?.needsAuth == undefined || options.needsAuth;
const needsCloud = options?.needsCloud === true;
const needsSubscription = options?.needsSubscription === true;
if (needsAuth) {
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
await this.revalidateAccessTokenIfExpired();
}
if (needsCloud) {
if (needsSubscription) {
if (!(await this.hasCloudSubscription())) {
throw new SubscriptionRequiredError();
}

View file

@ -1,43 +1,79 @@
import type { UserDetails } from "@types";
import type { ProfileVisibility, UserDetails } from "@types";
import { HydraApi } from "../hydra-api";
import {
userAuthRepository,
userSubscriptionRepository,
} from "@main/repository";
import * as Sentry from "@sentry/electron/main";
import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger";
export const getUserData = () => {
return HydraApi.get<UserDetails>(`/profile/me`).then(async (me) => {
userAuthRepository.upsert(
{
id: 1,
displayName: me.displayName,
profileImageUrl: me.profileImageUrl,
backgroundImageUrl: me.backgroundImageUrl,
userId: me.id,
},
["id"]
);
if (me.subscription) {
await userSubscriptionRepository.upsert(
return HydraApi.get<UserDetails>(`/profile/me`)
.then(async (me) => {
userAuthRepository.upsert(
{
id: 1,
subscriptionId: me.subscription?.id || "",
status: me.subscription?.status || "",
planId: me.subscription?.plan.id || "",
planName: me.subscription?.plan.name || "",
expiresAt: me.subscription?.expiresAt || null,
user: { id: 1 },
displayName: me.displayName,
profileImageUrl: me.profileImageUrl,
backgroundImageUrl: me.backgroundImageUrl,
userId: me.id,
},
["id"]
);
} else {
await userSubscriptionRepository.delete({ id: 1 });
}
Sentry.setUser({ id: me.id, username: me.username });
if (me.subscription) {
await userSubscriptionRepository.upsert(
{
id: 1,
subscriptionId: me.subscription?.id || "",
status: me.subscription?.status || "",
planId: me.subscription?.plan.id || "",
planName: me.subscription?.plan.name || "",
expiresAt: me.subscription?.expiresAt || null,
user: { id: 1 },
},
["id"]
);
} else {
await userSubscriptionRepository.delete({ id: 1 });
}
return me;
});
Sentry.setUser({ id: me.id, username: me.username });
return me;
})
.catch(async (err) => {
if (err instanceof UserNotLoggedInError) {
return null;
}
logger.error("Failed to get logged user", err);
const loggedUser = await userAuthRepository.findOne({
where: { id: 1 },
relations: { subscription: true },
});
if (loggedUser) {
return {
...loggedUser,
id: loggedUser.userId,
username: "",
bio: "",
profileVisibility: "PUBLIC" as ProfileVisibility,
subscription: loggedUser.subscription
? {
id: loggedUser.subscription.subscriptionId,
status: loggedUser.subscription.status,
plan: {
id: loggedUser.subscription.planId,
name: loggedUser.subscription.planName,
},
expiresAt: loggedUser.subscription.expiresAt,
}
: null,
} as UserDetails;
}
return null;
});
};