From 216f81377187ed7e42097f18658bd19a8d85d692 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 10 May 2025 17:43:09 +0100 Subject: [PATCH] feat: adding new friend session notification --- proto | 2 +- src/locales/en/translation.json | 5 +- src/locales/es/translation.json | 3 +- src/locales/pt-BR/translation.json | 5 +- src/locales/pt-PT/translation.json | 3 +- src/main/events/auth/sign-out.ts | 9 +- src/main/generated/envelope.ts | 497 +++++++----------- src/main/main.ts | 23 +- src/main/services/download/index.ts | 2 + src/main/services/hydra-api.ts | 6 +- src/main/services/index.ts | 3 + src/main/services/notifications/index.ts | 25 +- src/main/services/ws-manager.ts | 75 --- .../src/pages/catalogue/game-item.scss | 15 + .../src/pages/catalogue/game-item.tsx | 27 +- 15 files changed, 286 insertions(+), 414 deletions(-) delete mode 100644 src/main/services/ws-manager.ts diff --git a/proto b/proto index b8c2db16..7a23620f 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b8c2db166daf5ce215f957e237bfc160129f3eda +Subproject commit 7a23620f930f6fbb84c0abcaab5149a34ab4b4eb diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 30a3aa4c..5b5579ae 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -370,10 +370,11 @@ "restart_to_install_update": "Restart Hydra to install the update", "notification_achievement_unlocked_title": "Achievement unlocked for {{game}}", "notification_achievement_unlocked_body": "{{achievement}} and other {{count}} were unlocked", - "new_friend_request_description": "You have received a new friend request", + "new_friend_request_description": "{{displayName}} sent you a friend request", "new_friend_request_title": "New friend request", "extraction_complete": "Extraction complete", - "game_extracted": "{{title}} extracted successfully" + "game_extracted": "{{title}} extracted successfully", + "friend_started_playing_game": "{{displayName}} started playing a game" }, "system_tray": { "open": "Open Hydra", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index ef816d9f..f960b70e 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -371,7 +371,8 @@ "notification_achievement_unlocked_title": "Logro desbloqueado de {{game}}", "notification_achievement_unlocked_body": "{{achievement}} y otros {{count}} fueron desbloqueados", "new_friend_request_title": "Nueva solicitud de amistad", - "new_friend_request_description": "Has recibido una nueva solicitud de amistad" + "new_friend_request_description": "{{displayName}} te envió una solicitud de amistad", + "friend_started_playing_game": "{{displayName}} está jugando" }, "system_tray": { "open": "Abrir Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index db6af6ed..bf9c6e46 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -356,9 +356,10 @@ "new_update_available": "Versão {{version}} disponível", "restart_to_install_update": "Reinicie o Hydra para instalar a nova versão", "new_friend_request_title": "Novo pedido de amizade", - "new_friend_request_description": "Você recebeu um novo pedido de amizade", + "new_friend_request_description": "{{displayName}} te enviou um pedido de amizade", "extraction_complete": "Extração concluída", - "game_extracted": "{{title}} extraído com sucesso" + "game_extracted": "{{title}} extraído com sucesso", + "friend_started_playing_game": "{{displayName}} começou a jogar" }, "system_tray": { "open": "Abrir Hydra", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 01cb7d1b..6c32b35b 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -341,7 +341,8 @@ "new_update_available": "Versão {{version}} disponível", "restart_to_install_update": "Reinicia o Hydra para instalar a nova versão", "new_friend_request_title": "Novo pedido de amizade", - "new_friend_request_description": "Recebeste um novo pedido de amizade" + "new_friend_request_description": "{{displayName}} te enviou um pedido de amizade", + "friend_started_playing_game": "{{displayName}} começou a jogar" }, "system_tray": { "open": "Abrir o Hydra", diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index 2ab5e458..35a15d8b 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -1,5 +1,10 @@ import { registerEvent } from "../register-event"; -import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; +import { + DownloadManager, + HydraApi, + WSClient, + gamesPlaytime, +} from "@main/services"; import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; const signOut = async (_event: Electron.IpcMainInvokeEvent) => { @@ -30,6 +35,8 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => { databaseOperations, HydraApi.post("/auth/logout").catch(() => {}), ]); + + WSClient.close(); }; registerEvent("signOut", signOut); diff --git a/src/main/generated/envelope.ts b/src/main/generated/envelope.ts index 5431597e..18fb4909 100644 --- a/src/main/generated/envelope.ts +++ b/src/main/generated/envelope.ts @@ -14,348 +14,227 @@ import { MessageType } from "@protobuf-ts/runtime"; * @generated from protobuf message FriendRequest */ export interface FriendRequest { - /** - * @generated from protobuf field: int32 friend_request_count = 1; - */ - friendRequestCount: number; - /** - * @generated from protobuf field: optional string sender_id = 2; - */ - senderId?: string; + /** + * @generated from protobuf field: int32 friend_request_count = 1; + */ + friendRequestCount: number; + /** + * @generated from protobuf field: optional string sender_id = 2; + */ + senderId?: string; } /** - * @generated from protobuf message UpdateGamePlaytime + * @generated from protobuf message FriendGameSession */ -export interface UpdateGamePlaytime { - /** - * @generated from protobuf field: int64 playtime_delta_in_seconds = 1; - */ - playtimeDeltaInSeconds: bigint; - /** - * @generated from protobuf field: string last_time_played = 2; - */ - lastTimePlayed: string; - /** - * @generated from protobuf field: string game_id = 3; - */ - gameId: string; +export interface FriendGameSession { + /** + * @generated from protobuf field: string object_id = 1; + */ + objectId: string; + /** + * @generated from protobuf field: string shop = 2; + */ + shop: string; + /** + * @generated from protobuf field: string friend_id = 3; + */ + friendId: string; } /** * @generated from protobuf message Envelope */ export interface Envelope { - /** - * @generated from protobuf oneof: payload - */ - payload: - | { + /** + * @generated from protobuf oneof: payload + */ + payload: { oneofKind: "friendRequest"; /** * @generated from protobuf field: FriendRequest friend_request = 1; */ friendRequest: FriendRequest; - } - | { - oneofKind: "updateGamePlaytime"; + } | { + oneofKind: "friendGameSession"; /** - * @generated from protobuf field: UpdateGamePlaytime update_game_playtime = 2; + * @generated from protobuf field: FriendGameSession friend_game_session = 2; */ - updateGamePlaytime: UpdateGamePlaytime; - } - | { + friendGameSession: FriendGameSession; + } | { oneofKind: undefined; - }; + }; } // @generated message type with reflection information, may provide speed optimized methods class FriendRequest$Type extends MessageType { - constructor() { - super("FriendRequest", [ - { - no: 1, - name: "friend_request_count", - kind: "scalar", - T: 5 /*ScalarType.INT32*/, - }, - { - no: 2, - name: "sender_id", - kind: "scalar", - opt: true, - T: 9 /*ScalarType.STRING*/, - }, - ]); - } - create(value?: PartialMessage): FriendRequest { - const message = globalThis.Object.create(this.messagePrototype!); - message.friendRequestCount = 0; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: FriendRequest - ): FriendRequest { - const message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - const [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* int32 friend_request_count */ 1: - message.friendRequestCount = reader.int32(); - break; - case /* optional string sender_id */ 2: - message.senderId = reader.string(); - break; - default: - const u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` - ); - const d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d - ); - } + constructor() { + super("FriendRequest", [ + { no: 1, name: "friend_request_count", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, + { no: 2, name: "sender_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): FriendRequest { + const message = globalThis.Object.create((this.messagePrototype!)); + message.friendRequestCount = 0; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FriendRequest): FriendRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int32 friend_request_count */ 1: + message.friendRequestCount = reader.int32(); + break; + case /* optional string sender_id */ 2: + message.senderId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: FriendRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int32 friend_request_count = 1; */ + if (message.friendRequestCount !== 0) + writer.tag(1, WireType.Varint).int32(message.friendRequestCount); + /* optional string sender_id = 2; */ + if (message.senderId !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.senderId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; } - return message; - } - internalBinaryWrite( - message: FriendRequest, - writer: IBinaryWriter, - options: BinaryWriteOptions - ): IBinaryWriter { - /* int32 friend_request_count = 1; */ - if (message.friendRequestCount !== 0) - writer.tag(1, WireType.Varint).int32(message.friendRequestCount); - /* optional string sender_id = 2; */ - if (message.senderId !== undefined) - writer.tag(2, WireType.LengthDelimited).string(message.senderId); - const u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer - ); - return writer; - } } /** * @generated MessageType for protobuf message FriendRequest */ export const FriendRequest = new FriendRequest$Type(); // @generated message type with reflection information, may provide speed optimized methods -class UpdateGamePlaytime$Type extends MessageType { - constructor() { - super("UpdateGamePlaytime", [ - { - no: 1, - name: "playtime_delta_in_seconds", - kind: "scalar", - T: 3 /*ScalarType.INT64*/, - L: 0 /*LongType.BIGINT*/, - }, - { - no: 2, - name: "last_time_played", - kind: "scalar", - T: 9 /*ScalarType.STRING*/, - }, - { no: 3, name: "game_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - ]); - } - create(value?: PartialMessage): UpdateGamePlaytime { - const message = globalThis.Object.create(this.messagePrototype!); - message.playtimeDeltaInSeconds = 0n; - message.lastTimePlayed = ""; - message.gameId = ""; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: UpdateGamePlaytime - ): UpdateGamePlaytime { - const message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - const [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* int64 playtime_delta_in_seconds */ 1: - message.playtimeDeltaInSeconds = reader.int64().toBigInt(); - break; - case /* string last_time_played */ 2: - message.lastTimePlayed = reader.string(); - break; - case /* string game_id */ 3: - message.gameId = reader.string(); - break; - default: - const u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` - ); - const d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d - ); - } +class FriendGameSession$Type extends MessageType { + constructor() { + super("FriendGameSession", [ + { no: 1, name: "object_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "shop", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "friend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): FriendGameSession { + const message = globalThis.Object.create((this.messagePrototype!)); + message.objectId = ""; + message.shop = ""; + message.friendId = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FriendGameSession): FriendGameSession { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string object_id */ 1: + message.objectId = reader.string(); + break; + case /* string shop */ 2: + message.shop = reader.string(); + break; + case /* string friend_id */ 3: + message.friendId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: FriendGameSession, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string object_id = 1; */ + if (message.objectId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.objectId); + /* string shop = 2; */ + if (message.shop !== "") + writer.tag(2, WireType.LengthDelimited).string(message.shop); + /* string friend_id = 3; */ + if (message.friendId !== "") + writer.tag(3, WireType.LengthDelimited).string(message.friendId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; } - return message; - } - internalBinaryWrite( - message: UpdateGamePlaytime, - writer: IBinaryWriter, - options: BinaryWriteOptions - ): IBinaryWriter { - /* int64 playtime_delta_in_seconds = 1; */ - if (message.playtimeDeltaInSeconds !== 0n) - writer.tag(1, WireType.Varint).int64(message.playtimeDeltaInSeconds); - /* string last_time_played = 2; */ - if (message.lastTimePlayed !== "") - writer.tag(2, WireType.LengthDelimited).string(message.lastTimePlayed); - /* string game_id = 3; */ - if (message.gameId !== "") - writer.tag(3, WireType.LengthDelimited).string(message.gameId); - const u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer - ); - return writer; - } } /** - * @generated MessageType for protobuf message UpdateGamePlaytime + * @generated MessageType for protobuf message FriendGameSession */ -export const UpdateGamePlaytime = new UpdateGamePlaytime$Type(); +export const FriendGameSession = new FriendGameSession$Type(); // @generated message type with reflection information, may provide speed optimized methods class Envelope$Type extends MessageType { - constructor() { - super("Envelope", [ - { - no: 1, - name: "friend_request", - kind: "message", - oneof: "payload", - T: () => FriendRequest, - }, - { - no: 2, - name: "update_game_playtime", - kind: "message", - oneof: "payload", - T: () => UpdateGamePlaytime, - }, - ]); - } - create(value?: PartialMessage): Envelope { - const message = globalThis.Object.create(this.messagePrototype!); - message.payload = { oneofKind: undefined }; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: Envelope - ): Envelope { - const message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - const [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* FriendRequest friend_request */ 1: - message.payload = { - oneofKind: "friendRequest", - friendRequest: FriendRequest.internalBinaryRead( - reader, - reader.uint32(), - options, - (message.payload as any).friendRequest - ), - }; - break; - case /* UpdateGamePlaytime update_game_playtime */ 2: - message.payload = { - oneofKind: "updateGamePlaytime", - updateGamePlaytime: UpdateGamePlaytime.internalBinaryRead( - reader, - reader.uint32(), - options, - (message.payload as any).updateGamePlaytime - ), - }; - break; - default: - const u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` - ); - const d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d - ); - } + constructor() { + super("Envelope", [ + { no: 1, name: "friend_request", kind: "message", oneof: "payload", T: () => FriendRequest }, + { no: 2, name: "friend_game_session", kind: "message", oneof: "payload", T: () => FriendGameSession } + ]); + } + create(value?: PartialMessage): Envelope { + const message = globalThis.Object.create((this.messagePrototype!)); + message.payload = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Envelope): Envelope { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* FriendRequest friend_request */ 1: + message.payload = { + oneofKind: "friendRequest", + friendRequest: FriendRequest.internalBinaryRead(reader, reader.uint32(), options, (message.payload as any).friendRequest) + }; + break; + case /* FriendGameSession friend_game_session */ 2: + message.payload = { + oneofKind: "friendGameSession", + friendGameSession: FriendGameSession.internalBinaryRead(reader, reader.uint32(), options, (message.payload as any).friendGameSession) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Envelope, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* FriendRequest friend_request = 1; */ + if (message.payload.oneofKind === "friendRequest") + FriendRequest.internalBinaryWrite(message.payload.friendRequest, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* FriendGameSession friend_game_session = 2; */ + if (message.payload.oneofKind === "friendGameSession") + FriendGameSession.internalBinaryWrite(message.payload.friendGameSession, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; } - return message; - } - internalBinaryWrite( - message: Envelope, - writer: IBinaryWriter, - options: BinaryWriteOptions - ): IBinaryWriter { - /* FriendRequest friend_request = 1; */ - if (message.payload.oneofKind === "friendRequest") - FriendRequest.internalBinaryWrite( - message.payload.friendRequest, - writer.tag(1, WireType.LengthDelimited).fork(), - options - ).join(); - /* UpdateGamePlaytime update_game_playtime = 2; */ - if (message.payload.oneofKind === "updateGamePlaytime") - UpdateGamePlaytime.internalBinaryWrite( - message.payload.updateGamePlaytime, - writer.tag(2, WireType.LengthDelimited).fork(), - options - ).join(); - const u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer - ); - return writer; - } } /** * @generated MessageType for protobuf message Envelope diff --git a/src/main/main.ts b/src/main/main.ts index 3a9c4084..0669d6f2 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,16 +1,21 @@ -import { Aria2, DownloadManager, Ludusavi, startMainLoop } from "./services"; -import { RealDebridClient } from "./services/download/real-debrid"; -import { HydraApi } from "./services/hydra-api"; -import { uploadGamesBatch } from "./services/library-sync"; import { downloadsSublevel } from "./level/sublevels/downloads"; import { sortBy } from "lodash-es"; import { Downloader } from "@shared"; import { levelKeys, db } from "./level"; import type { UserPreferences } from "@types"; -import { TorBoxClient } from "./services/download/torbox"; -import { CommonRedistManager } from "./services/common-redist-manager"; -import { WSManager } from "./services/ws-manager"; -import { SystemPath } from "./services/system-path"; +import { + WSClient, + SystemPath, + CommonRedistManager, + TorBoxClient, + RealDebridClient, + Aria2, + DownloadManager, + Ludusavi, + HydraApi, + uploadGamesBatch, + startMainLoop, +} from "@main/services"; export const loadState = async () => { SystemPath.checkIfPathsAreAvailable(); @@ -38,7 +43,7 @@ export const loadState = async () => { await HydraApi.setupApi().then(() => { uploadGamesBatch(); - WSManager.connect(); + WSClient.connect(); }); const downloads = await downloadsSublevel diff --git a/src/main/services/download/index.ts b/src/main/services/download/index.ts index b8396b66..f4e2eddc 100644 --- a/src/main/services/download/index.ts +++ b/src/main/services/download/index.ts @@ -1 +1,3 @@ export * from "./download-manager"; +export * from "./real-debrid"; +export * from "./torbox"; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 04fe7a72..122960c7 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -11,7 +11,7 @@ import { getUserData } from "./user/get-user-data"; import { db } from "@main/level"; import { levelKeys } from "@main/level/sublevels"; import type { Auth, User } from "@types"; -import { WSManager } from "./ws-manager"; +import { WSClient } from "./ws/ws-client"; interface HydraApiOptions { needsAuth?: boolean; @@ -102,8 +102,8 @@ export class HydraApi { WindowManager.mainWindow.webContents.send("on-signin"); await clearGamesRemoteIds(); uploadGamesBatch(); - WSManager.close(); - WSManager.connect(); + WSClient.close(); + WSClient.connect(); } } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 164b7b8d..3d1ab69e 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -12,3 +12,6 @@ export * from "./7zip"; export * from "./game-files-manager"; export * from "./common-redist-manager"; export * from "./aria2"; +export * from "./ws"; +export * from "./system-path"; +export * from "./library-sync"; diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 49f952b6..77866a47 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -10,7 +10,7 @@ import icon from "@resources/icon.png?asset"; import { NotificationOptions, toXmlString } from "./xml"; import { logger } from "../logger"; import { WindowManager } from "../window-manager"; -import type { Game, UserPreferences } from "@types"; +import type { Game, GameStats, UserPreferences, UserProfile } from "@types"; import { db, levelKeys } from "@main/level"; import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update"; import { SystemPath } from "../system-path"; @@ -82,7 +82,7 @@ export const publishNotificationUpdateReadyToInstall = async ( }; export const publishNewFriendRequestNotification = async ( - senderProfileImageUrl?: string + user: UserProfile ) => { const userPreferences = await db.get( levelKeys.userPreferences, @@ -99,9 +99,26 @@ export const publishNewFriendRequestNotification = async ( }), body: t("new_friend_request_description", { ns: "notifications", + displayName: user.displayName, }), - icon: senderProfileImageUrl - ? await downloadImage(senderProfileImageUrl) + icon: user?.profileImageUrl + ? await downloadImage(user.profileImageUrl) + : trayIcon, + }).show(); +}; + +export const publishFriendStartedPlayingGameNotification = async ( + friend: UserProfile, + game: GameStats +) => { + new Notification({ + title: t("friend_started_playing_game", { + ns: "notifications", + displayName: friend.displayName, + }), + body: game.assets?.title, + icon: friend?.profileImageUrl + ? await downloadImage(friend.profileImageUrl) : trayIcon, }).show(); }; diff --git a/src/main/services/ws-manager.ts b/src/main/services/ws-manager.ts deleted file mode 100644 index c43a4d82..00000000 --- a/src/main/services/ws-manager.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { WebSocket } from "ws"; -import { HydraApi } from "./hydra-api"; -import { Envelope } from "@main/generated/envelope"; -import { logger } from "./logger"; -import { WindowManager } from "./window-manager"; - -export class WSManager { - private static ws: WebSocket | null = null; - private static reconnectInterval = 1000; - private static maxReconnectInterval = 30000; - private static reconnectAttempts = 0; - private static reconnecting = false; - - static async connect() { - const { token } = await HydraApi.post<{ token: string }>("/auth/ws"); - - this.ws = new WebSocket(import.meta.env.MAIN_VITE_WS_URL, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - this.ws.on("open", () => { - logger.info("WS connected"); - this.reconnectInterval = 1000; - this.reconnecting = false; - }); - - this.ws.on("message", (message) => { - const envelope = Envelope.fromBinary( - new Uint8Array(Buffer.from(message.toString())) - ); - - if (envelope.payload.oneofKind === "friendRequest") { - WindowManager.mainWindow?.webContents.send("on-sync-friend-requests", { - friendRequestCount: envelope.payload.friendRequest.friendRequestCount, - }); - } - }); - - this.ws.on("close", () => { - logger.warn("WS closed. Attempting reconnect..."); - this.tryReconnect(); - }); - - this.ws.on("error", (err) => { - logger.error("WS error:", err); - this.tryReconnect(); - }); - } - - private static async tryReconnect() { - if (this.reconnecting) return; - - this.reconnecting = true; - this.reconnectAttempts++; - - const waitTime = Math.min( - this.reconnectInterval * 2 ** this.reconnectAttempts, - this.maxReconnectInterval - ); - logger.info(`Reconnecting in ${waitTime / 1000}s...`); - - setTimeout(() => { - this.connect(); - }, waitTime); - } - - public static async close() { - if (this.ws) { - this.ws.close(); - this.ws = null; - } - } -} diff --git a/src/renderer/src/pages/catalogue/game-item.scss b/src/renderer/src/pages/catalogue/game-item.scss index 83d182f4..f49bcbf8 100644 --- a/src/renderer/src/pages/catalogue/game-item.scss +++ b/src/renderer/src/pages/catalogue/game-item.scss @@ -25,6 +25,21 @@ border-right: 1px solid globals.$border-color; } + &__cover-placeholder { + display: flex; + align-items: center; + justify-content: center; + color: globals.$body-color; + width: 200px; + height: 103px; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 50%, + rgba(255, 255, 255, 0.1) 100% + ); + } + &__details { display: flex; flex-direction: column; diff --git a/src/renderer/src/pages/catalogue/game-item.tsx b/src/renderer/src/pages/catalogue/game-item.tsx index 85fa2115..69915f20 100644 --- a/src/renderer/src/pages/catalogue/game-item.tsx +++ b/src/renderer/src/pages/catalogue/game-item.tsx @@ -7,6 +7,7 @@ import { useNavigate } from "react-router-dom"; import "./game-item.scss"; import { useTranslation } from "react-i18next"; import { CatalogueSearchResult } from "@types"; +import { QuestionIcon } from "@primer/octicons-react"; export interface GameItemProps { game: CatalogueSearchResult; @@ -43,18 +44,32 @@ export function GameItem({ game }: GameItemProps) { }); }, [game.genres, language, steamGenres]); + const libraryImage = useMemo(() => { + if (game.libraryImageUrl) { + return ( + {game.title} + ); + } + + return ( +
+ +
+ ); + }, [game.libraryImageUrl, game.title]); + return (