diff --git a/package.json b/package.json index 54438fe2..f6e04071 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.6.0", + "version": "3.6.1", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b01268a3..24de88cf 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -515,7 +515,8 @@ "earned_points": "Earned points", "show_achievements_on_profile": "Show your achievements on your profile", "show_points_on_profile": "Show your earned points on your profile", - "error_adding_friend": "Could not send friend request. Please check friend code" + "error_adding_friend": "Could not send friend request. Please check friend code", + "friend_code_length_error": "Friend code must have 8 characters" }, "achievement": { "achievement_unlocked": "Achievement unlocked", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index a45af998..8373b415 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -508,7 +508,8 @@ "earned_points": "Pontos ganhos", "show_achievements_on_profile": "Exiba suas conquistas no perfil", "show_points_on_profile": "Exiba seus pontos ganhos no perfil", - "error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido" + "error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido", + "friend_code_length_error": "Código de amigo deve ter 8 caracteres" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", diff --git a/src/main/events/catalogue/save-game-shop-assets.ts b/src/main/events/catalogue/save-game-shop-assets.ts index 5fdba628..f0ec4343 100644 --- a/src/main/events/catalogue/save-game-shop-assets.ts +++ b/src/main/events/catalogue/save-game-shop-assets.ts @@ -8,7 +8,9 @@ const saveGameShopAssets = async ( shop: GameShop, assets: ShopAssets ): Promise => { - return gamesShopAssetsSublevel.put(levelKeys.game(shop, objectId), assets); + const key = levelKeys.game(shop, objectId); + const existingAssets = await gamesShopAssetsSublevel.get(key); + return gamesShopAssetsSublevel.put(key, { ...existingAssets, ...assets }); }; registerEvent("saveGameShopAssets", saveGameShopAssets); diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index 5b74cc8c..01495a39 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -1,13 +1,13 @@ import { registerEvent } from "../register-event"; import type { GameShop } from "@types"; import { createGame } from "@main/services/library-sync"; -import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements"; import { downloadsSublevel, gamesShopAssetsSublevel, gamesSublevel, levelKeys, } from "@main/level"; +import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager"; const addGameToLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -43,7 +43,10 @@ const addGameToLibrary = async ( await createGame(game).catch(() => {}); - updateLocalUnlockedAchievements(game); + AchievementWatcherManager.firstSyncWithRemoteIfNeeded( + game.shop, + game.objectId + ); }; registerEvent("addGameToLibrary", addGameToLibrary); diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts index b3d2daa2..e63afcec 100644 --- a/src/main/events/library/reset-game-achievements.ts +++ b/src/main/events/library/reset-game-achievements.ts @@ -16,7 +16,8 @@ const resetGameAchievements = async ( objectId: string ) => { try { - const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); + const levelKey = levelKeys.game(shop, objectId); + const game = await gamesSublevel.get(levelKey); if (!game) return; @@ -29,8 +30,6 @@ const resetGameAchievements = async ( } } - const levelKey = levelKeys.game(game.shop, game.objectId); - await gameAchievementsSublevel .get(levelKey) .then(async (gameAchievements) => { diff --git a/src/main/events/user/get-compared-unlocked-achievements.ts b/src/main/events/user/get-compared-unlocked-achievements.ts index 697ad716..be641f2a 100644 --- a/src/main/events/user/get-compared-unlocked-achievements.ts +++ b/src/main/events/user/get-compared-unlocked-achievements.ts @@ -3,6 +3,7 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import { db, levelKeys } from "@main/level"; +import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager"; const getComparedUnlockedAchievements = async ( _event: Electron.IpcMainInvokeEvent, @@ -10,6 +11,8 @@ const getComparedUnlockedAchievements = async ( shop: GameShop, userId: string ) => { + await AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId); + const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index 21aad7a0..b8677894 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -2,6 +2,7 @@ import type { GameShop, UserAchievement, UserPreferences } from "@types"; import { registerEvent } from "../register-event"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; +import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager"; export const getUnlockedAchievements = async ( objectId: string, @@ -62,7 +63,7 @@ export const getUnlockedAchievements = async ( !achievementData.hidden || showHiddenAchievementsDescription ? achievementData.description : undefined, - } as UserAchievement; + }; }) .sort((a, b) => { if (a.unlocked && !b.unlocked) return -1; @@ -79,6 +80,7 @@ const getUnlockedAchievementsEvent = async ( objectId: string, shop: GameShop ): Promise => { + AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId); return getUnlockedAchievements(objectId, shop, false); }; diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 5cf09d4f..0a0482b2 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -10,6 +10,7 @@ import { import type { AchievementFile, Game, + GameShop, UnlockedAchievement, UserPreferences, } from "@types"; @@ -18,7 +19,7 @@ import { Cracker } from "@shared"; import { publishCombinedNewAchievementNotification } from "../notifications"; import { db, gamesSublevel, levelKeys } from "@main/level"; import { WindowManager } from "../window-manager"; -import { sleep } from "@main/helpers"; +import { setTimeout } from "node:timers/promises"; const fileStats: Map = new Map(); const fltFiles: Map> = new Map(); @@ -37,7 +38,7 @@ const watchAchievementsWindows = async () => { const gameAchievementFiles: AchievementFile[] = []; for (const objectId of getAlternativeObjectIds(game.objectId)) { - gameAchievementFiles.push(...(achievementFiles.get(objectId) || [])); + gameAchievementFiles.push(...(achievementFiles.get(objectId) ?? [])); gameAchievementFiles.push( ...findAchievementFileInExecutableDirectory(game) @@ -127,6 +128,11 @@ const compareFile = (game: Game, file: AchievementFile) => { ); return processAchievementFileDiff(game, file); } catch (err) { + achievementsLogger.error( + "Error reading file", + file.filePath, + err instanceof Error ? err.message : err + ); fileStats.set(file.filePath, -1); return; } @@ -136,20 +142,69 @@ const processAchievementFileDiff = async ( game: Game, file: AchievementFile ) => { - const unlockedAchievements = parseAchievementFile(file.filePath, file.type); + const parsedAchievements = parseAchievementFile(file.filePath, file.type); - if (unlockedAchievements.length) { - return mergeAchievements(game, unlockedAchievements, true); + if (parsedAchievements.length) { + return mergeAchievements(game, parsedAchievements, true); } return 0; }; export class AchievementWatcherManager { - private static hasFinishedMergingWithRemote = false; + private static _hasFinishedPreSearch = false; + + public static get hasFinishedPreSearch() { + return this._hasFinishedPreSearch; + } + + public static readonly alreadySyncedGames: Map = new Map(); + + public static async firstSyncWithRemoteIfNeeded( + shop: GameShop, + objectId: string + ) { + const gameKey = levelKeys.game(shop, objectId); + if (this.alreadySyncedGames.get(gameKey)) return; + + const game = await gamesSublevel.get(gameKey).catch(() => null); + if (!game || game.isDeleted) return; + + const gameAchievementFiles = findAchievementFiles(game); + + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); + + gameAchievementFiles.push(...achievementFileInsideDirectory); + + const unlockedAchievements: UnlockedAchievement[] = []; + + for (const achievementFile of gameAchievementFiles) { + const localAchievementFile = parseAchievementFile( + achievementFile.filePath, + achievementFile.type + ); + + if (localAchievementFile.length) { + unlockedAchievements.push(...localAchievementFile); + } + } + + this.alreadySyncedGames.set(gameKey, true); + + const newAchievements = await mergeAchievements( + game, + unlockedAchievements, + false + ); + + if (newAchievements > 0) { + this.notifyCombinedAchievementsUnlocked(1, newAchievements); + } + } public static watchAchievements() { - if (!this.hasFinishedMergingWithRemote) return; + if (!this.hasFinishedPreSearch) return; if (process.platform === "win32") { return watchAchievementsWindows(); @@ -188,7 +243,11 @@ export class AchievementWatcherManager { } } - return mergeAchievements(game, unlockedAchievements, false); + if (unlockedAchievements.length) { + return mergeAchievements(game, unlockedAchievements, false); + } + + return 0; } private static async getGameAchievementFilesWindows() { @@ -237,25 +296,44 @@ export class AchievementWatcherManager { ); } - public static async preSearchAchievements() { - await sleep(2000); + private static async notifyCombinedAchievementsUnlocked( + totalNewGamesWithAchievements: number, + totalNewAchievements: number + ) { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + if (userPreferences.achievementCustomNotificationsEnabled !== false) { + WindowManager.notificationWindow?.webContents.send( + "on-combined-achievements-unlocked", + totalNewGamesWithAchievements, + totalNewAchievements, + userPreferences.achievementCustomNotificationPosition ?? "top-left" + ); + } else { + publishCombinedNewAchievementNotification( + totalNewAchievements, + totalNewGamesWithAchievements + ); + } + } + + public static async preSearchAchievements() { try { const gameAchievementFiles = process.platform === "win32" ? await this.getGameAchievementFilesWindows() : await this.getGameAchievementFilesLinux(); - const newAchievementsCount: number[] = []; - - for (const { game, achievementFiles } of gameAchievementFiles) { - const result = await this.preProcessGameAchievementFiles( - game, - achievementFiles - ); - - newAchievementsCount.push(result); - } + const newAchievementsCount = await Promise.all( + gameAchievementFiles.map(({ game, achievementFiles }) => { + return this.preProcessGameAchievementFiles(game, achievementFiles); + }) + ); const totalNewGamesWithAchievements = newAchievementsCount.filter( (achievements) => achievements @@ -267,34 +345,16 @@ export class AchievementWatcherManager { ); if (totalNewAchievements > 0) { - const userPreferences = await db.get( - levelKeys.userPreferences, - { - valueEncoding: "json", - } + await setTimeout(4000); + this.notifyCombinedAchievementsUnlocked( + totalNewGamesWithAchievements, + totalNewAchievements ); - - if (userPreferences.achievementNotificationsEnabled !== false) { - if (userPreferences.achievementCustomNotificationsEnabled !== false) { - WindowManager.notificationWindow?.webContents.send( - "on-combined-achievements-unlocked", - totalNewGamesWithAchievements, - totalNewAchievements, - userPreferences.achievementCustomNotificationPosition ?? - "top-left" - ); - } else { - publishCombinedNewAchievementNotification( - totalNewAchievements, - totalNewGamesWithAchievements - ); - } - } } } catch (err) { achievementsLogger.error("Error on preSearchAchievements", err); } - this.hasFinishedMergingWithRemote = true; + this._hasFinishedPreSearch = true; } } diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 9fb1977d..7a531388 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -303,7 +303,7 @@ export const findAchievementFileInExecutableDirectory = ( "achievements.ini" ), }, - ]; + ].filter((file) => fs.existsSync(file.filePath)) as AchievementFile[]; }; const mapFileLocationWithObjectId = ( diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index f4d66b6a..6b7bdeff 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -1,24 +1,39 @@ import { HydraApi } from "../hydra-api"; -import type { GameShop, SteamAchievement } from "@types"; +import type { GameAchievement, GameShop, SteamAchievement } from "@types"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; +import { AxiosError } from "axios"; + +const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes + +const getModifiedSinceHeader = ( + cachedAchievements: GameAchievement | undefined +): Date | undefined => { + if (!cachedAchievements) { + return undefined; + } + + return cachedAchievements.updatedAt + ? new Date(cachedAchievements.updatedAt) + : undefined; +}; export const getGameAchievementData = async ( objectId: string, shop: GameShop, useCachedData: boolean ) => { - const cachedAchievements = await gameAchievementsSublevel.get( - levelKeys.game(shop, objectId) - ); + const gameKey = levelKeys.game(shop, objectId); - if (cachedAchievements && useCachedData) + const cachedAchievements = await gameAchievementsSublevel.get(gameKey); + + if (cachedAchievements?.achievements && useCachedData) return cachedAchievements.achievements; if ( - cachedAchievements && - Date.now() < (cachedAchievements.cacheExpiresTimestamp ?? 0) + cachedAchievements?.achievements && + Date.now() < (cachedAchievements.updatedAt ?? 0) + LOCAL_CACHE_EXPIRATION ) { return cachedAchievements.achievements; } @@ -29,18 +44,22 @@ export const getGameAchievementData = async ( }) .then((language) => language || "en"); - return HydraApi.get("/games/achievements", { - shop, - objectId, - language, - }) + return HydraApi.get( + "/games/achievements", + { + shop, + objectId, + language, + }, + { + ifModifiedSince: getModifiedSinceHeader(cachedAchievements), + } + ) .then(async (achievements) => { - await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), { + await gameAchievementsSublevel.put(gameKey, { unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [], achievements, - cacheExpiresTimestamp: achievements.length - ? Date.now() + 1000 * 60 * 30 // 30 minutes - : undefined, + updatedAt: Date.now() + LOCAL_CACHE_EXPIRATION, }); return achievements; @@ -50,8 +69,14 @@ export const getGameAchievementData = async ( throw err; } + const isNotModified = (err as AxiosError)?.response?.status === 304; + + if (isNotModified) { + return cachedAchievements?.achievements ?? []; + } + logger.error("Failed to get game achievements for", objectId, err); - return []; + return cachedAchievements?.achievements ?? []; }); }; diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 2674e451..f2ea03ac 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -14,6 +14,7 @@ import { SubscriptionRequiredError } from "@shared"; import { achievementsLogger } from "../logger"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; import { getGameAchievementData } from "./get-game-achievement-data"; +import { AchievementWatcherManager } from "./achievement-watcher-manager"; const isRareAchievement = (points: number) => { const rawPercentage = (50 - Math.sqrt(points)) * 2; @@ -35,7 +36,7 @@ const saveAchievementsOnLocal = async ( await gameAchievementsSublevel.put(levelKey, { achievements: gameAchievement?.achievements ?? [], unlockedAchievements: unlockedAchievements, - cacheExpiresTimestamp: gameAchievement?.cacheExpiresTimestamp, + updatedAt: gameAchievement?.updatedAt, }); if (!sendUpdateEvent) return; @@ -56,9 +57,9 @@ export const mergeAchievements = async ( achievements: UnlockedAchievement[], publishNotification: boolean ) => { - let localGameAchievement = await gameAchievementsSublevel.get( - levelKeys.game(game.shop, game.objectId) - ); + const gameKey = levelKeys.game(game.shop, game.objectId); + + let localGameAchievement = await gameAchievementsSublevel.get(gameKey); const userPreferences = await db.get( levelKeys.userPreferences, { @@ -67,10 +68,8 @@ export const mergeAchievements = async ( ); if (!localGameAchievement) { - await getGameAchievementData(game.objectId, game.shop, true); - localGameAchievement = await gameAchievementsSublevel.get( - levelKeys.game(game.shop, game.objectId) - ); + await getGameAchievementData(game.objectId, game.shop, false); + localGameAchievement = await gameAchievementsSublevel.get(gameKey); } const achievementsData = localGameAchievement?.achievements ?? []; @@ -136,6 +135,12 @@ export const mergeAchievements = async ( }; }); + achievementsLogger.log( + "Publishing achievement notification", + game.objectId, + game.title + ); + if (userPreferences.achievementCustomNotificationsEnabled !== false) { WindowManager.notificationWindow?.webContents.send( "on-achievement-unlocked", @@ -153,7 +158,11 @@ export const mergeAchievements = async ( } } - if (game.remoteId) { + const shouldSyncWithRemote = + game.remoteId && + (newAchievements.length || AchievementWatcherManager.hasFinishedPreSearch); + + if (shouldSyncWithRemote) { await HydraApi.put( "/profile/games/achievements", { @@ -194,8 +203,11 @@ export const mergeAchievements = async ( mergedLocalAchievements, publishNotification ); + }) + .finally(() => { + AchievementWatcherManager.alreadySyncedGames.set(gameKey, true); }); - } else { + } else if (newAchievements.length) { await saveAchievementsOnLocal( game.objectId, game.shop, diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts deleted file mode 100644 index 44f2693a..00000000 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - findAchievementFiles, - findAchievementFileInExecutableDirectory, -} from "./find-achivement-files"; -import { parseAchievementFile } from "./parse-achievement-file"; -import { mergeAchievements } from "./merge-achievements"; -import type { Game, UnlockedAchievement } from "@types"; - -export const updateLocalUnlockedAchievements = async (game: Game) => { - const gameAchievementFiles = findAchievementFiles(game); - - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); - - gameAchievementFiles.push(...achievementFileInsideDirectory); - - const unlockedAchievements: UnlockedAchievement[] = []; - - for (const achievementFile of gameAchievementFiles) { - const localAchievementFile = parseAchievementFile( - achievementFile.filePath, - achievementFile.type - ); - - if (localAchievementFile.length) { - unlockedAchievements.push(...localAchievementFile); - } - } - - mergeAchievements(game, unlockedAchievements, false); -}; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 0f5a4d21..0e6de36b 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -16,6 +16,7 @@ import { WSClient } from "./ws/ws-client"; interface HydraApiOptions { needsAuth?: boolean; needsSubscription?: boolean; + ifModifiedSince?: Date; } interface HydraApiUserAuth { @@ -337,8 +338,13 @@ export class HydraApi { ) { await this.validateOptions(options); + const headers = { + ...this.getAxiosConfig().headers, + "If-Modified-Since": options?.ifModifiedSince?.toUTCString(), + }; + return this.instance - .get(url, { params, ...this.getAxiosConfig() }) + .get(url, { params, ...this.getAxiosConfig(), headers }) .then((response) => response.data) .catch(this.handleUnauthorizedError); } diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index 74ef9c77..b9d10f52 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -13,9 +13,8 @@ export const mergeWithRemoteGames = async () => { return HydraApi.get("/profile/games") .then(async (response) => { for (const game of response) { - const localGame = await gamesSublevel.get( - levelKeys.game(game.shop, game.objectId) - ); + const gameKey = levelKeys.game(game.shop, game.objectId); + const localGame = await gamesSublevel.get(gameKey); if (localGame) { const updatedLastTimePlayed = @@ -30,7 +29,7 @@ export const mergeWithRemoteGames = async () => { ? game.playTimeInMilliseconds : localGame.playTimeInMilliseconds; - await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + await gamesSublevel.put(gameKey, { ...localGame, remoteId: game.id, lastTimePlayed: updatedLastTimePlayed, @@ -38,7 +37,7 @@ export const mergeWithRemoteGames = async () => { favorite: game.isFavorite ?? localGame.favorite, }); } else { - await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + await gamesSublevel.put(gameKey, { objectId: game.objectId, title: game.title, remoteId: game.id, @@ -51,20 +50,17 @@ export const mergeWithRemoteGames = async () => { }); } - await gamesShopAssetsSublevel.put( - levelKeys.game(game.shop, game.objectId), - { - shop: game.shop, - objectId: game.objectId, - title: game.title, - coverImageUrl: game.coverImageUrl, - libraryHeroImageUrl: game.libraryHeroImageUrl, - libraryImageUrl: game.libraryImageUrl, - logoImageUrl: game.logoImageUrl, - iconUrl: game.iconUrl, - logoPosition: game.logoPosition, - } - ); + await gamesShopAssetsSublevel.put(gameKey, { + shop: game.shop, + objectId: game.objectId, + title: game.title, + coverImageUrl: game.coverImageUrl, + libraryHeroImageUrl: game.libraryHeroImageUrl, + libraryImageUrl: game.libraryImageUrl, + logoImageUrl: game.logoImageUrl, + iconUrl: game.iconUrl, + logoPosition: game.logoPosition, + }); } }) .catch(() => {}); diff --git a/src/main/services/library-sync/upload-games-batch.ts b/src/main/services/library-sync/upload-games-batch.ts index 984521db..837fb48a 100644 --- a/src/main/services/library-sync/upload-games-batch.ts +++ b/src/main/services/library-sync/upload-games-batch.ts @@ -34,9 +34,7 @@ export const uploadGamesBatch = async () => { await mergeWithRemoteGames(); - if (HydraApi.isLoggedIn()) { - AchievementWatcherManager.preSearchAchievements(); - } + AchievementWatcherManager.preSearchAchievements(); if (WindowManager.mainWindow) WindowManager.mainWindow.webContents.send("on-library-batch-complete"); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 7a2433bc..4d8b2a80 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -8,6 +8,7 @@ import { gamesSublevel, levelKeys } from "@main/level"; import { CloudSync } from "./cloud-sync"; import { logger } from "./logger"; import path from "path"; +import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager"; export const gamesPlaytime = new Map< string, @@ -190,6 +191,11 @@ export const watchProcesses = async () => { function onOpenGame(game: Game) { const now = performance.now(); + AchievementWatcherManager.firstSyncWithRemoteIfNeeded( + game.shop, + game.objectId + ); + gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { lastTick: now, firstTick: now, diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 3d84d6f3..35a52397 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -370,14 +370,11 @@ export class WindowManager { }, }); this.notificationWindow.setIgnoreMouseEvents(true); - // this.notificationWindow.setVisibleOnAllWorkspaces(true, { - // visibleOnFullScreen: true, - // }); this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); this.loadNotificationWindowURL(); - if (isStaging) { + if (!app.isPackaged || isStaging) { this.notificationWindow.webContents.openDevTools(); } } @@ -464,7 +461,7 @@ export class WindowManager { editorWindow.once("ready-to-show", () => { editorWindow.show(); this.mainWindow?.webContents.openDevTools(); - if (isStaging) { + if (!app.isPackaged || isStaging) { editorWindow.webContents.openDevTools(); } }); diff --git a/src/renderer/src/components/text-field/text-field.scss b/src/renderer/src/components/text-field/text-field.scss index 6defc3c1..56756cda 100644 --- a/src/renderer/src/components/text-field/text-field.scss +++ b/src/renderer/src/components/text-field/text-field.scss @@ -74,6 +74,6 @@ } &__error-label { - color: globals.$danger-color; + color: globals.$error-color; } } diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index e58ed673..e1774e60 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -183,10 +183,13 @@ export function GameDetailsContextProvider({ .catch(() => {}); } - window.electron.syncGameByObjectId(shop, objectId).then(() => { - if (abortController.signal.aborted) return; - updateGame(); - }); + window.electron + .syncGameByObjectId(shop, objectId) + .then(() => { + if (abortController.signal.aborted) return; + updateGame(); + }) + .catch(() => {}); }, [ updateGame, dispatch, diff --git a/src/renderer/src/pages/achievements/achievements.scss b/src/renderer/src/pages/achievements/achievements.scss index e208a810..9b1deea5 100644 --- a/src/renderer/src/pages/achievements/achievements.scss +++ b/src/renderer/src/pages/achievements/achievements.scss @@ -158,8 +158,8 @@ $logo-max-width: 200px; &-points { display: flex; align-items: center; + justify-content: end; gap: 4px; - margin-right: 4px; font-weight: 600; &--locked { diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index dcbdc4ac..c5c37933 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -55,7 +55,6 @@ export function AchievementNotification() { isHidden: false, isRare: false, isPlatinum: false, - points: 0, iconUrl: "https://cdn.losbroxas.org/favicon.svg", }, ]); diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx index a597c809..84248522 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx @@ -51,6 +51,14 @@ export const UserFriendModalAddFriend = ({ } }; + const validateFriendCode = (callback: () => void) => { + if (friendCode.length === 8) { + return callback(); + } + + showErrorToast(t("friend_code_length_error")); + }; + const handleCancelFriendRequest = (userId: string) => { updateFriendRequestState(userId, "CANCEL").catch(() => { showErrorToast(t("try_again")); @@ -91,13 +99,13 @@ export const UserFriendModalAddFriend = ({ disabled={isAddingFriend} className="user-friend-modal-add-friend__button" type="button" - onClick={handleClickAddFriend} + onClick={() => validateFriendCode(handleClickAddFriend)} > {isAddingFriend ? t("sending") : t("add")}